├── .gitattributes ├── .gitignore ├── LICENSE.txt ├── css └── style.css ├── index.html ├── js ├── XCalc.js └── index.js └── readme.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | bin/ 9 | tmp/ 10 | *.tmp 11 | *.bak 12 | *.swp 13 | *~.nib 14 | local.properties 15 | .classpath 16 | .settings/ 17 | .loadpath 18 | 19 | # External tool builders 20 | .externalToolBuilders/ 21 | 22 | # Locally stored "Eclipse launch configurations" 23 | *.launch 24 | 25 | # CDT-specific 26 | .cproject 27 | 28 | # PDT-specific 29 | .buildpath 30 | 31 | 32 | ################# 33 | ## Visual Studio 34 | ################# 35 | 36 | ## Ignore Visual Studio temporary files, build results, and 37 | ## files generated by popular Visual Studio add-ons. 38 | 39 | # User-specific files 40 | *.suo 41 | *.user 42 | *.sln.docstates 43 | 44 | # Build results 45 | 46 | [Dd]ebug/ 47 | [Rr]elease/ 48 | x64/ 49 | build/ 50 | [Bb]in/ 51 | [Oo]bj/ 52 | 53 | # MSTest test Results 54 | [Tt]est[Rr]esult*/ 55 | [Bb]uild[Ll]og.* 56 | 57 | *_i.c 58 | *_p.c 59 | *.ilk 60 | *.meta 61 | *.obj 62 | *.pch 63 | *.pdb 64 | *.pgc 65 | *.pgd 66 | *.rsp 67 | *.sbr 68 | *.tlb 69 | *.tli 70 | *.tlh 71 | *.tmp 72 | *.tmp_proj 73 | *.log 74 | *.vspscc 75 | *.vssscc 76 | .builds 77 | *.pidb 78 | *.log 79 | *.scc 80 | 81 | # Visual C++ cache files 82 | ipch/ 83 | *.aps 84 | *.ncb 85 | *.opensdf 86 | *.sdf 87 | *.cachefile 88 | 89 | # Visual Studio profiler 90 | *.psess 91 | *.vsp 92 | *.vspx 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | 101 | # TeamCity is a build add-in 102 | _TeamCity* 103 | 104 | # DotCover is a Code Coverage Tool 105 | *.dotCover 106 | 107 | # NCrunch 108 | *.ncrunch* 109 | .*crunch*.local.xml 110 | 111 | # Installshield output folder 112 | [Ee]xpress/ 113 | 114 | # DocProject is a documentation generator add-in 115 | DocProject/buildhelp/ 116 | DocProject/Help/*.HxT 117 | DocProject/Help/*.HxC 118 | DocProject/Help/*.hhc 119 | DocProject/Help/*.hhk 120 | DocProject/Help/*.hhp 121 | DocProject/Help/Html2 122 | DocProject/Help/html 123 | 124 | # Click-Once directory 125 | publish/ 126 | 127 | # Publish Web Output 128 | *.Publish.xml 129 | *.pubxml 130 | 131 | # NuGet Packages Directory 132 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 133 | #packages/ 134 | 135 | # Windows Azure Build Output 136 | csx 137 | *.build.csdef 138 | 139 | # Windows Store app package directory 140 | AppPackages/ 141 | 142 | # Others 143 | sql/ 144 | *.Cache 145 | ClientBin/ 146 | [Ss]tyle[Cc]op.* 147 | ~$* 148 | *~ 149 | *.dbmdl 150 | *.[Pp]ublish.xml 151 | *.pfx 152 | *.publishsettings 153 | 154 | # RIA/Silverlight projects 155 | Generated_Code/ 156 | 157 | # Backup & report files from converting an old project file to a newer 158 | # Visual Studio version. Backup files are not needed, because we have git ;-) 159 | _UpgradeReport_Files/ 160 | Backup*/ 161 | UpgradeLog*.XML 162 | UpgradeLog*.htm 163 | 164 | # SQL Server files 165 | App_Data/*.mdf 166 | App_Data/*.ldf 167 | 168 | ############# 169 | ## Windows detritus 170 | ############# 171 | 172 | # Windows image file caches 173 | Thumbs.db 174 | ehthumbs.db 175 | 176 | # Folder config file 177 | Desktop.ini 178 | 179 | # Recycle Bin used on file shares 180 | $RECYCLE.BIN/ 181 | 182 | # Mac crap 183 | .DS_Store 184 | 185 | 186 | ############# 187 | ## Python 188 | ############# 189 | 190 | *.py[co] 191 | 192 | # Packages 193 | *.egg 194 | *.egg-info 195 | dist/ 196 | build/ 197 | eggs/ 198 | parts/ 199 | var/ 200 | sdist/ 201 | develop-eggs/ 202 | .installed.cfg 203 | 204 | # Installer logs 205 | pip-log.txt 206 | 207 | # Unit test / coverage reports 208 | .coverage 209 | .tox 210 | 211 | #Translations 212 | *.mo 213 | 214 | #Mr Developer 215 | .mr.developer.cfg 216 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | @import url(http://fonts.googleapis.com/css?family=Open+Sans:300); 2 | 3 | body { 4 | background-color:#34495e; 5 | color:#FFF; 6 | font-family: sans-serif; 7 | padding:0; 8 | margin:0; 9 | text-align:center; 10 | } 11 | #github { 12 | position:absolute; 13 | top:0; 14 | right:0; 15 | padding:20px; 16 | background-color:#123; 17 | color:#FFF; 18 | text-decoration:none; 19 | } 20 | #github:hover { 21 | background-color:#234; 22 | } 23 | #top { 24 | padding:20px; 25 | display:inline-block; 26 | } 27 | h1 { 28 | font-family: 'Open Sans', sans-serif; 29 | font-size:30px; 30 | margin:0 0 10px 0; 31 | } 32 | input[type="submit"], input[type="button"], a.button { 33 | background-color:#FFF; 34 | color:#000; 35 | padding:5px; 36 | border: 5px solid #FFF; 37 | text-decoration:none; 38 | display:inline-block; 39 | height:40px; 40 | font-size:16px; 41 | font-family:sans-serif; 42 | margin:0 0 0 10px; 43 | } 44 | input[type="submit"]:hover, input[type="button"]:hover, a.button:hover { 45 | border: 5px solid #DDD; 46 | } 47 | input[type="text"] { 48 | display:inline-block; 49 | font-size: inherit; 50 | padding: 10px; 51 | border: 0; 52 | } 53 | input[type="text"]:focus { 54 | outline: 0; 55 | background-color:#EEE; 56 | } 57 | #function { 58 | margin-bottom:20px; 59 | } 60 | #function label { 61 | display:inline-block; 62 | height:40px; 63 | line-height:40px; 64 | margin-right:5px; 65 | } 66 | #function input[type="text"] { 67 | margin:0; 68 | width:85%; 69 | display:inline-block; 70 | height:20px; 71 | float:right; 72 | } 73 | #range { 74 | color: #9AB; 75 | } 76 | #range input[type="text"] { 77 | width:30px; 78 | height:15px; 79 | text-align:center; 80 | color: #9AB; 81 | background-color:#2c3e50; 82 | } 83 | #range input[type="text"]::-webkit-input-placeholder{ 84 | color: #9AB; 85 | } 86 | #range input[type="text"]:-moz-placeholder{ 87 | color: #9AB; 88 | } 89 | #range input[type="text"]::-moz-placeholder { 90 | color: #9AB; 91 | } 92 | #range input[type="text"]:-ms-input-placeholder { 93 | color: #9AB; 94 | } 95 | #range input[type="text"]:focus { 96 | background-color:#2e4052; 97 | } 98 | .rangeSection { 99 | display:inline-block; 100 | } 101 | #graph { 102 | margin:20px auto 0 auto; 103 | width: 100%; 104 | height:50px; 105 | } 106 | #result { 107 | margin-top:10px; 108 | border-top:20px solid #2c3e50; 109 | border-bottom:20px solid #2c3e50; 110 | overflow:hidden; 111 | background-color:#2c3e50; 112 | text-align:center; 113 | max-height:0; 114 | margin-bottom:20px; 115 | -webkit-transition: all 0.3s ease-out; 116 | -moz-transition: all 0.3s ease-out; 117 | -ms-transition: all 0.3s ease-out; 118 | -o-transition: all 0.3s ease-out; 119 | transition: all 0.3s ease-out; 120 | } 121 | .open #result { 122 | max-height:2000px; 123 | padding:20px; 124 | color:#FFF; 125 | } 126 | canvas { 127 | margin-bottom: 50px; 128 | } 129 | canvas:last-child { 130 | margin-bottom: 10px; 131 | } 132 | .answer { 133 | clear:both; 134 | text-align:center; 135 | } 136 | .final { 137 | color:#FFF; 138 | font-size:60px; 139 | text-align:left; 140 | font-family: 'Open Sans', sans-serif; 141 | } 142 | .error { 143 | background-color:#6B1010; 144 | padding:10px; 145 | opacity:0.7; 146 | display:inline-block; 147 | width:500px; 148 | text-align:left; 149 | } 150 | 151 | /* Pretty formula styles */ 152 | .formula { 153 | font-family: 'Open Sans', sans-serif; 154 | padding:0 10px; 155 | margin-bottom:10px; 156 | } 157 | .group { 158 | display:inline-block; 159 | vertical-align:baseline; 160 | text-align:center; 161 | margin:1px 1px 0 0; 162 | padding:0; 163 | } 164 | .division { 165 | vertical-align: middle; 166 | } 167 | .operator { 168 | padding:5px; 169 | } 170 | .exponent { 171 | font-size: 0.7em; 172 | vertical-align:super; 173 | } 174 | .denom { 175 | clear:both; 176 | display:block; 177 | border-top:1px solid #FFF; 178 | padding:0 20px; 179 | } 180 | 181 | @media all and (max-width:700px) { 182 | #github { 183 | position:relative; 184 | width:auto; 185 | display:block; 186 | } 187 | h1 { 188 | font-size:24px; 189 | } 190 | } 191 | 192 | @media all and (max-width:500px) { 193 | #top { 194 | display:block; 195 | position:relative; 196 | } 197 | #function label { 198 | width:10%; 199 | text-align:right; 200 | } 201 | #function input[type="text"] { 202 | width:70%; 203 | } 204 | .rangeSection { 205 | display:block; 206 | margin-bottom:5px; 207 | } 208 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | XCalc.js Demo 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |

XCalc.js Demo

22 |
23 | 24 |
25 |
26 |
From < x <
27 |
and < y <
28 |
29 | 30 |
31 |
32 |
33 |
34 | Find this on Github! 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /js/XCalc.js: -------------------------------------------------------------------------------- 1 | //Module for input checking and parsing 2 | var XCalc = (function() { 3 | 4 | //Class for operators 5 | function Operator(input) { 6 | this.operator = input; 7 | if (!input) { 8 | XCalc.log("Operator has no input."); 9 | } 10 | 11 | this.solve = function(segment1, segment2, x) { 12 | var v1 = segment1.coefficient; 13 | if (segment1.type=="variable") { 14 | v1 = x; 15 | } 16 | var v2 = segment2.coefficient; 17 | if (segment2.type=="variable") { 18 | v2 = x; 19 | } 20 | if (this.operator=="+") { 21 | return new Segment(v1 + v2); 22 | } else if (this.operator=="-") { 23 | return new Segment(v1 - v2); 24 | } else if (this.operator=="*") { 25 | return new Segment(v1 * v2); 26 | } else if (this.operator=="/") { 27 | return new Segment(v1 / v2); 28 | } else if (this.operator=="^") { 29 | return new Segment(Math.pow(v1, v2)); 30 | } 31 | }; 32 | } 33 | 34 | //Class for special functions 35 | function MathFunction(input) { 36 | this.f=input; 37 | if (!input) { 38 | XCalc.log("Math function has no input."); 39 | } 40 | 41 | this.solve = function(segment) { 42 | var v = segment.coefficient; 43 | if (this.f=="sin") { 44 | return new Segment(Math.sin(v)); 45 | } else if (this.f=="cos") { 46 | return new Segment(Math.cos(v)); 47 | } else if (this.f=="tan") { 48 | return new Segment(Math.tan(v)); 49 | } else if (this.f=="asin") { 50 | return new Segment(Math.asin(v)); 51 | } else if (this.f=="acos") { 52 | return new Segment(Math.acos(v)); 53 | } else if (this.f=="atan") { 54 | return new Segment(Math.atan(v)); 55 | } else if (this.f=="abs") { 56 | return new Segment(Math.abs(v)); 57 | } else if (this.f=="log") { 58 | return new Segment(Math.log(v)/Math.log(10)); 59 | } else if (this.f=="ln") { 60 | return new Segment(Math.log(v)); 61 | } 62 | }; 63 | } 64 | 65 | //Class for constants 66 | function MathConstant(input) { 67 | this.c=input; 68 | if (!input) { 69 | XCalc.log("Math function has no input."); 70 | } 71 | 72 | this.solve = function() { 73 | if (this.c=="pi") { 74 | return new Segment(Math.PI); 75 | } else if (this.c=="e") { 76 | return new Segment(Math.E); 77 | } 78 | }; 79 | 80 | this.prettyC = function() { 81 | if (this.c=="pi") { 82 | return "π"; 83 | } else if (this.c=="e") { 84 | return "e"; 85 | } 86 | }; 87 | } 88 | 89 | //Class for a segment of math (a container) 90 | function Segment(input) { 91 | this.sections = []; 92 | this.type="section"; 93 | this.operator=0; 94 | this.coefficient=0; 95 | this.mathFunction=0; 96 | this.mathConstant=0; 97 | this.variable=""; 98 | 99 | var removeBrackets = function(value) { 100 | if (!value) return ""; 101 | 102 | //While there are brackets around the string 103 | while (value.substr(0, 1)=="(" && value.substr(value.length-1, 1)==")") { 104 | var openBrackets=1; 105 | 106 | //See if the end bracket closes the opening bracket or not 107 | for (var i=1; i0; i++) { 108 | if (value.substr(i, 1)=="(") openBrackets++; 109 | if (value.substr(i, 1)==")") openBrackets--; 110 | } 111 | i-=1; 112 | 113 | //If it corresponds to different brackets, do nothing 114 | if (openBrackets!==0 || i!=value.length-1) { 115 | break; 116 | 117 | //Otherwise, remove the brackets, continue loop to see if there are more 118 | } else { 119 | value=value.substring(1, value.length-1); 120 | } 121 | } 122 | 123 | return value; 124 | }; 125 | 126 | var findLast = function(operator, value) { 127 | 128 | //Keep searching for the next last sign if the one found is within brackets 129 | var inBrackets=true; 130 | var index=-1; 131 | if (operator!="^") { 132 | index=value.lastIndexOf(operator); 133 | } else { 134 | index=value.indexOf(operator); //Look for the first instead of last if it's an exponent 135 | } 136 | var operators="+-/*^"; 137 | while (inBrackets) { 138 | var openBrackets=0; 139 | 140 | //Find how many brackets are opened or closed at a given point in the string 141 | for (var i=0; i0 && operators.indexOf(value.substr(i-1, 1))==-1) || i===0)) || (openBrackets==1 && operator=="(")) { 152 | inBrackets=false; 153 | break; 154 | 155 | //Otherwise, find the next operator, and loop through again to see if that one is in brackets 156 | } else { 157 | if (operator!="^") { 158 | index = value.substring(0, index).lastIndexOf(operator); 159 | } else { 160 | var nextOperator = value.substring(index+1).indexOf(operator); 161 | if (nextOperator==-1) { 162 | index=-1; 163 | } else { 164 | index = (index+1+value.substring(index+1).indexOf(operator)); 165 | } 166 | } 167 | } 168 | } 169 | } 170 | 171 | //If no more operators are found, break the loop 172 | if (index==-1) { 173 | inBrackets=false; 174 | } 175 | } 176 | return index; 177 | }; 178 | 179 | var findLastTrig = function(trig, value) { 180 | var matches=0; 181 | var index=-1; 182 | var r=0; 183 | if (trig=="sin") { 184 | r=/(a)?sin/g; 185 | } else if (trig=="cos") { 186 | r=/(a)?cos/g; 187 | } else if (trig=="tan") { 188 | r=/(a)?tan/g; 189 | } else { 190 | return -1; 191 | } 192 | for (matches=r.exec(value); matches; matches=r.exec(value)) if (RegExp.$1 != "a") index=matches.index; 193 | var inBrackets=true; 194 | while (inBrackets && index!=-1) { 195 | var openBrackets=0; 196 | 197 | //Find how many brackets are opened or closed at a given point in the string 198 | for (var i=0; iindexOpen && indexClosed!=-1) { 286 | return indexClosed; 287 | } else { 288 | return indexOpen; 289 | } 290 | }; 291 | 292 | this.containsX = function() { 293 | if (this.type=="variable") { 294 | return true; 295 | } else if (this.type=="value") { 296 | return false; 297 | } else { 298 | if (this.sections.length==1) { 299 | return this.sections[0].containsX(); 300 | } else if (this.sections.length==2) { 301 | return this.sections[0].containsX() || this.sections[1].containsX(); 302 | } 303 | } 304 | }; 305 | 306 | this.containsConstant = function() { 307 | if (this.type=="constant") { 308 | return true; 309 | } else if (this.type=="value") { 310 | return false; 311 | } else { 312 | if (this.sections.length==1) { 313 | return this.sections[0].containsConstant(); 314 | } else if (this.sections.length==2) { 315 | return this.sections[0].containsConstant() || this.sections[1].containsConstant(); 316 | } 317 | } 318 | }; 319 | 320 | this.equals = function(expression) { 321 | if (this.type != expression.type) { 322 | return false; 323 | } else { 324 | if (this.type=="function") { 325 | return (this.mathFunction.f==expression.mathFunction.f && this.sections[0].equals(expression.sections[0])); 326 | } else if (this.type=="variable") { 327 | return (this.variable==expression.variable && this.coefficient==expression.coefficient); 328 | } else if (this.type=="constant") { 329 | return (this.mathConstant.c==expression.mathConstant.c); 330 | } else if (this.type=="value") { 331 | return this.coefficient==expression.coefficient; 332 | } else if (this.type=="section") { 333 | if (this.operator.operator=="*" || this.operator.operator=="+") { 334 | return (this.operator.operator==expression.operator.operator && ((this.sections[0].equals(expression.sections[0]) && this.sections[1].equals(expression.sections[1])) || (this.sections[0].equals(expression.sections[1]) && this.sections[1].equals(expression.sections[0])))); 335 | } else { 336 | return (this.operator.operator==expression.operator.operator && this.sections[0].equals(expression.sections[0]) && this.sections[1].equals(expression.sections[1])); 337 | } 338 | } 339 | } 340 | }; 341 | 342 | //Recursively solve children 343 | this.solve = function(x) { 344 | if (!x) x=0; 345 | if (this.type=="value") { 346 | return this; 347 | } else if (this.type=="variable") { 348 | return new Segment(x); 349 | } else if (this.type=="constant") { 350 | return this.mathConstant.solve(); 351 | } else if (this.type=="function") { 352 | return this.mathFunction.solve(this.sections[0].solve(x)); 353 | } else { 354 | if (this.sections.length==1) { 355 | return this.sections[0].solve(x); 356 | } else if (this.sections.length==2) { 357 | return this.operator.solve(this.sections[0].solve(x), this.sections[1].solve(x), x); 358 | } 359 | } 360 | }; 361 | 362 | //Outputs the final answer 363 | this.result = function(x) { 364 | return this.solve(x).coefficient; 365 | }; 366 | 367 | this.simplify = function() { 368 | var expression = new Segment(this); 369 | var finalExpression; 370 | 371 | //If the segment is a variable, leave it as is. 372 | //If it contains a variable in its subsections, simplify subsections 373 | if (expression.containsX() && expression.type!="variable") { 374 | for (var i=0; i"; 836 | 837 | if (this.type=="value") { 838 | str += this.coefficient; 839 | } else if (this.type=="variable") { 840 | str += "x"; 841 | } else if (this.type=="constant") { 842 | str += this.mathConstant.prettyC(); 843 | } else if (this.type=="function") { 844 | str += this.mathFunction.f + "(" + this.sections[0].prettyFormula() + ")"; 845 | } else if (this.type=="section") { 846 | if (this.sections[0].type=="section" && this.sections[0].operator.operator!="/") { 847 | str+= "(" + this.sections[0].prettyFormula() + ")"; 848 | } else { 849 | str+= this.sections[0].prettyFormula(); 850 | } 851 | if (this.operator.operator=="*") { 852 | str += "×"; 853 | } else if (this.operator.operator!="^" && this.operator.operator!="/") { 854 | str += "" + this.operator.operator + ""; 855 | } 856 | if (this.operator.operator=="^") { 857 | str += "
" + this.sections[1].prettyFormula() + "
"; 858 | } else if (this.operator.operator == "/") { 859 | str += "
" + this.sections[1].prettyFormula() + "
"; 860 | } else { 861 | if (this.sections[1].type=="section" && this.sections[0].operator.operator!="/") { 862 | str+= "(" + this.sections[1].prettyFormula() + ")"; 863 | } else { 864 | str+= this.sections[1].prettyFormula(); 865 | } 866 | } 867 | } 868 | str+=""; 869 | return str; 870 | }; 871 | 872 | //constructor: parse the string 873 | if (input!==undefined) { 874 | if (typeof(input)=="string") { 875 | //Remove excess whitespace 876 | input = input.replace(/\s/g, ""); 877 | 878 | //get rid of unnecessary brackets surrounding the section 879 | input = removeBrackets(input); 880 | 881 | //Find the last instance of each operator in the string 882 | var addition = findLast("+", input); 883 | var subtraction = findLast("-", input); 884 | var division = findLast("/", input); 885 | var exponent = findLast("^", input); //Find the first exponent, since those work in reverse 886 | var bracket1 = findLast("(", input); 887 | 888 | var sin = findLastTrig("sin", input); 889 | var cos = findLastTrig("cos", input); 890 | var tan = findLastTrig("tan", input); 891 | var asin = findLast("asin", input); 892 | var acos = findLast("acos", input); 893 | var atan = findLast("atan", input); 894 | var abs = findLast("abs", input); 895 | var log = findLast("log", input); 896 | var ln = findLast("ln", input); 897 | var sqrt = findLast("sqrt", input); 898 | var e = findLast("e", input); 899 | var pi = findLast("pi", input); 900 | var multiplication = findLast("*", input); 901 | var multiplication2 = findMultiplicationBrackets(input); //Find brackets that are the same as multiplication 902 | var xMultiplication = findLast("x", input); 903 | var xMultiplication2 = findLast("X", input); 904 | if (xMultiplication2>xMultiplication) xMultiplication = xMultiplication2; 905 | var functionMultiplication = -1; 906 | var operators="+-/*^sincostanabsloglnsqrt"; 907 | if (sin>multiplication && (sin===0 || operators.indexOf(input.substr(sin-1, 1))==-1)) functionMultiplication=sin; 908 | if (cos>multiplication && (cos===0 || operators.indexOf(input.substr(cos-1, 1))==-1)) functionMultiplication=cos; 909 | if (tan>multiplication && (tan===0 || operators.indexOf(input.substr(tan-1, 1))==-1)) functionMultiplication=tan; 910 | if (asin>multiplication && (asin===0 || operators.indexOf(input.substr(asin-1, 1))==-1)) functionMultiplication=asin; 911 | if (acos>multiplication && (acos===0 || operators.indexOf(input.substr(acos-1, 1))==-1)) functionMultiplication=acos; 912 | if (atan>multiplication && (atan===0 || operators.indexOf(input.substr(atan-1, 1))==-1)) functionMultiplication=atan; 913 | if (abs>multiplication && (abs===0 || operators.indexOf(input.substr(abs-1, 1))==-1)) functionMultiplication=abs; 914 | if (log>multiplication && (log===0 || operators.indexOf(input.substr(log-1, 1))==-1)) functionMultiplication=log; 915 | if (ln>multiplication && (ln===0 || operators.indexOf(input.substr(ln-1, 1))==-1)) functionMultiplication=ln; 916 | if (sqrt>multiplication && (sqrt===0 || operators.indexOf(input.substr(sqrt-1, 1))==-1)) functionMultiplication=sqrt; 917 | if (xMultiplication>multiplication && (xMultiplication===0 || operators.indexOf(input.substr(xMultiplication-1, 1))==-1)) functionMultiplication=xMultiplication; 918 | if (e>multiplication && (e===0 || operators.indexOf(input.substr(e-1, 1))==-1)) functionMultiplication=e; 919 | if (pi>multiplication && (pi===0 || operators.indexOf(input.substr(pi-1, 1))==-1)) functionMultiplication=e; 920 | 921 | //Push back each half of the equation into a section, in reverse order of operations 922 | if (addition != -1 && addition>subtraction) { 923 | this.sections.push(new Segment(input.substring(0, addition))); 924 | this.sections.push(new Segment(input.substring(addition+1))); 925 | this.operator = new Operator("+"); 926 | } else if (subtraction != -1) { 927 | if (subtraction>0) { 928 | this.sections.push(new Segment(input.substring(0, subtraction))); 929 | } else { 930 | this.sections.push(new Segment(0)); 931 | } 932 | this.sections.push(new Segment(input.substring(subtraction+1))); 933 | this.operator = new Operator("-"); 934 | } else if (functionMultiplication > 0 && Math.max(functionMultiplication, multiplication, multiplication2, division)==functionMultiplication) { 935 | this.sections.push(new Segment(input.substring(0, functionMultiplication))); 936 | this.sections.push(new Segment(input.substring(functionMultiplication))); 937 | this.operator = new Operator("*"); 938 | } else if (multiplication2 != -1 && Math.max(multiplication2, multiplication, division)==multiplication2) { 939 | this.sections.push(new Segment(input.substring(0, multiplication2))); 940 | this.sections.push(new Segment(input.substring(multiplication2))); 941 | this.operator = new Operator("*"); 942 | } else if (multiplication != -1 && multiplication>division) { 943 | this.sections.push(new Segment(input.substring(0, multiplication))); 944 | this.sections.push(new Segment(input.substring(multiplication+1))); 945 | this.operator = new Operator("*"); 946 | } else if (division != -1) { 947 | this.sections.push(new Segment(input.substring(0, division))); 948 | this.sections.push(new Segment(input.substring(division+1))); 949 | this.operator = new Operator("/"); 950 | } else if (exponent != -1) { 951 | this.sections.push(new Segment(input.substring(0, exponent))); 952 | this.sections.push(new Segment(input.substring(exponent+1))); 953 | this.operator = new Operator("^"); 954 | } else if (sin != -1 && Math.max(sin, cos, tan, asin, acos, atan, abs, log, ln, sqrt)==sin) { 955 | this.sections.push(new Segment(input.substring(sin+3))); 956 | this.mathFunction = new MathFunction("sin"); 957 | this.type = "function"; 958 | } else if (cos != -1 && Math.max(cos, tan, asin, acos, atan, abs, log, ln, sqrt)==cos) { 959 | this.sections.push(new Segment(input.substring(cos+3))); 960 | this.mathFunction = new MathFunction("cos"); 961 | this.type = "function"; 962 | } else if (tan != -1 && Math.max(tan, asin, acos, atan, abs, log, ln, sqrt)==tan) { 963 | this.sections.push(new Segment(input.substring(tan+3))); 964 | this.mathFunction = new MathFunction("tan"); 965 | this.type = "function"; 966 | } else if (asin != -1 && Math.max(asin, acos, atan, abs, log, ln, sqrt)==asin) { 967 | this.sections.push(new Segment(input.substring(asin+4))); 968 | this.mathFunction = new MathFunction("asin"); 969 | this.type = "function"; 970 | } else if (acos != -1 && Math.max(acos, atan, abs, log, ln, sqrt)==acos) { 971 | this.sections.push(new Segment(input.substring(acos+4))); 972 | this.mathFunction = new MathFunction("acos"); 973 | this.type = "function"; 974 | } else if (atan != -1 && Math.max(atan, abs, log, ln, sqrt)==atan) { 975 | this.sections.push(new Segment(input.substring(atan+4))); 976 | this.mathFunction = new MathFunction("atan"); 977 | this.type = "function"; 978 | } else if (abs != -1 && Math.max(abs, log, ln, sqrt)==abs) { 979 | this.sections.push(new Segment(input.substring(abs+3))); 980 | this.mathFunction = new MathFunction("abs"); 981 | this.type = "function"; 982 | } else if (log != -1 && Math.max(log, ln, sqrt)==log) { 983 | this.sections.push(new Segment(input.substring(log+3))); 984 | this.mathFunction = new MathFunction("log"); 985 | this.type = "function"; 986 | } else if (ln != -1 && Math.max(ln, sqrt)==ln) { 987 | this.sections.push(new Segment(input.substring(ln+2))); 988 | this.mathFunction = new MathFunction("ln"); 989 | this.type = "function"; 990 | } else if (sqrt != -1) { 991 | this.sections.push(new Segment(input.substring(sqrt+4))); 992 | this.sections.push(new Segment("0.5")); 993 | this.operator = new Operator("^"); 994 | } else if (bracket1 != -1) { 995 | var openBrackets=1; 996 | for (var i=bracket1+1; i0; i++) { 997 | if (input.substr(i, 1)=="(") openBrackets++; 998 | if (input.substr(i, 1)==")") openBrackets--; 999 | } 1000 | if (openBrackets===0) { 1001 | var bracket2=i-1; 1002 | if (bracket1>0) this.sections.push(new Segment(input.substring(0, bracket1))); 1003 | if (bracket2-bracket1!=1) this.sections.push(new Segment(input.substring(bracket1+1, bracket2))); 1004 | if (bracket2!=input.length-1) this.sections.push(new Segment(input.substring(bracket2+1))); 1005 | this.operator = new Operator("*"); 1006 | } else { 1007 | XCalc.log("Brackets nesting error: " + input); 1008 | } 1009 | 1010 | } else if (e != -1 && e>pi) { 1011 | if (e>0) { 1012 | this.sections.push(new Segment(input.substring(0, e))); 1013 | this.sections.push(new Segment("e")); 1014 | this.operator=new Operator("*"); 1015 | } else { 1016 | this.mathConstant = new MathConstant("e"); 1017 | this.type="constant"; 1018 | } 1019 | } else if (pi != -1) { 1020 | if (pi>0) { 1021 | this.sections.push(new Segment(input.substring(0, pi))); 1022 | this.sections.push(new Segment("pi")); 1023 | this.operator=new Operator("*"); 1024 | } else { 1025 | this.mathConstant = new MathConstant("pi"); 1026 | this.type="constant"; 1027 | } 1028 | 1029 | //If there are no operators, just push the input itself 1030 | } else { 1031 | var xLocation=input.toLowerCase().indexOf("x"); 1032 | if (xLocation!=-1) { 1033 | if (xLocation>0) { 1034 | this.sections.push(new Segment(input.substring(0, xLocation))); 1035 | this.sections.push(new Segment("x")); 1036 | this.operator=new Operator("*"); 1037 | } else { 1038 | this.variable="x"; 1039 | this.type="variable"; 1040 | } 1041 | } else { 1042 | this.coefficient = parseFloat(input); 1043 | this.type = "value"; 1044 | } 1045 | } 1046 | } else if (typeof(input)=="number") { 1047 | this.coefficient = input; 1048 | this.type = "value"; 1049 | } else if (input.simplify) { 1050 | this.coefficient = input.coefficient; 1051 | this.operator = input.operator; 1052 | this.mathFunction = input.mathFunction; 1053 | this.mathConstant = input.mathConstant; 1054 | this.variable = input.variable; 1055 | this.type=input.type; 1056 | this.sections=[]; 1057 | for (var j=0; j0) { 1114 | while (i=zeroes.length) { 1116 | return 0; 1117 | } 1118 | _min = points[zeroes[i].x].y; 1119 | for (++i; i0) { 1129 | while (isNaN(points[i].y) || points[i].y === undefined || Math.abs(points[i].y) == Infinity) i++; 1130 | _min = points[i].y; 1131 | for (++i; i0) { 1154 | while (i<=zeroes.length && points[zeroes[i].x] === undefined) i++; 1155 | if (i>=zeroes.length) return 0; 1156 | _max = points[zeroes[i].x].y; 1157 | for (++i; i_max) { 1159 | _max = points[zeroes[i].x].y; 1160 | } 1161 | } 1162 | max=_max; 1163 | return max; 1164 | 1165 | //Otherwise, find the max of the given points normally 1166 | } else if (points.length>0) { 1167 | while (isNaN(points[i].y) || points[i].y === undefined || Math.abs(points[i].y) == Infinity) i++; 1168 | _max = points[i].y; 1169 | for (++i; i_max) { 1171 | _max = points[i].y; 1172 | } 1173 | } 1174 | max=_max; 1175 | return max; 1176 | } else { 1177 | return 0; 1178 | } 1179 | } else { 1180 | return max; 1181 | } 1182 | }; 1183 | 1184 | //See if the distance between adjacent points is small enough to be graphable 1185 | var checkCurvature = function(i) { 1186 | return (points[i-1] && Math.pow((points[i].y-points[i-1].y)/(y2-y1)*canvas.height, 2) + Math.pow((points[i].x-points[i-1].x)/(points[points.length-1].x-points[0].x)*canvas.width, 2)<=3); 1187 | }.bind(this); 1188 | 1189 | //Add points in between existing points 1190 | var addDetail = function(i) { 1191 | 1192 | //Next to undefined points or next to points where the curvature is too big, add a point in between as long as the distance between the x values isn't too small 1193 | while (Math.abs((points[i+1].x-points[i].x)/(points[points.length-1].x-points[0].x))*canvas.width>=0.03 && !((!(points[i].y===undefined || isNaN(points[i].y) || Math.abs(points[i].y) == Infinity) && !(points[i+1].y===undefined || isNaN(points[i+1].y) || Math.abs(points[i+1].y) == Infinity)) && (points[i].y>y2 || points[i].yy2 || points[i+1].y10000) { 1197 | newP.y=undefined; 1198 | good=false; 1199 | } 1200 | points.splice(i+1, 0, newP); 1201 | if (!good) break; 1202 | } 1203 | }.bind(this); 1204 | 1205 | //Updates the points and graph 1206 | this.update = function() { 1207 | var accuracy = (x2-x1)/canvas.width; 1208 | points = []; 1209 | for (var i=x1; i<=x2; i+=accuracy) { 1210 | points.push(new Point(i, this.expression.result(i))); 1211 | if (points[points.length-1].y!==undefined && Math.abs(points[points.length-1].y)>100000) { 1212 | points[points.length-1].y=undefined; 1213 | } 1214 | } 1215 | 1216 | max=undefined; 1217 | min=undefined; 1218 | 1219 | if (autoRange) { 1220 | 1221 | //Find approximate zeroes of the first derivative 1222 | var zeroes = []; 1223 | for (i=0; i100000) { 1231 | y1=-100; 1232 | y2=100; 1233 | } else { 1234 | y1=this.getMin(zeroes)-5; 1235 | y2=this.getMax(zeroes)+5; 1236 | } 1237 | autoRange = false; 1238 | } 1239 | 1240 | for (i=0; i=_x1-30 && 0<=_x2+30) { 1255 | stage.lineWidth=2; 1256 | stage.beginPath(); 1257 | stage.moveTo(canvas.width/2-(((_x2+_x1)/2)/(_x2-_x1))*canvas.width, 0); 1258 | stage.lineTo(canvas.width/2-(((_x2+_x1)/2)/(_x2-_x1))*canvas.width, canvas.height); 1259 | stage.closePath(); 1260 | stage.stroke(); 1261 | stage.textAlign = "right"; 1262 | stage.textBaseline="middle"; 1263 | 1264 | //Draw ticks and numbers on x axis 1265 | stage.lineWidth=1; 1266 | limit = (Math.abs(_y2)>Math.abs(_y1))?Math.abs(_y2):Math.abs(_y1); 1267 | for (i=0; i<=limit; i+=Math.pow(10, Math.floor(Math.log(_y2-_y1) / Math.LN10))/2) { 1268 | if (i===0) continue; 1269 | if (i<=_y2+50) { 1270 | stage.beginPath(); 1271 | stage.moveTo(canvas.width/2-(((_x2+_x1)/2)/(_x2-_x1))*canvas.width-5, canvas.height-((i-_y1)/(_y2-_y1))*canvas.height); 1272 | stage.lineTo(canvas.width/2-(((_x2+_x1)/2)/(_x2-_x1))*canvas.width+5, canvas.height-((i-_y1)/(_y2-_y1))*canvas.height); 1273 | stage.closePath(); 1274 | stage.stroke(); 1275 | stage.fillText(""+(Math.round(i*100)/100), canvas.width/2-(((_x2+_x1)/2)/(_x2-_x1))*canvas.width-8, canvas.height-((i-_y1)/(_y2-_y1))*canvas.height); 1276 | } 1277 | 1278 | if (i>=_y1-50) { 1279 | stage.beginPath(); 1280 | stage.moveTo(canvas.width/2-(((_x2+_x1)/2)/(_x2-_x1))*canvas.width-5, canvas.height-((-i-_y1)/(_y2-_y1))*canvas.height); 1281 | stage.lineTo(canvas.width/2-(((_x2+_x1)/2)/(_x2-_x1))*canvas.width+5, canvas.height-((-i-_y1)/(_y2-_y1))*canvas.height); 1282 | stage.closePath(); 1283 | stage.stroke(); 1284 | stage.fillText(""+(Math.round(-i*100)/100), canvas.width/2-(((_x2+_x1)/2)/(_x2-_x1))*canvas.width-8, canvas.height-((-i-_y1)/(_y2-_y1))*canvas.height); 1285 | } 1286 | } 1287 | } 1288 | 1289 | //Draw the x axis if it is in the view 1290 | if (0>=_y1-50 && 0<=_y2+50) { 1291 | stage.lineWidth=2; 1292 | stage.beginPath(); 1293 | stage.moveTo(0, canvas.height/2+(((_y2+_y1)/2)/(_y2-_y1))*canvas.height); 1294 | stage.lineTo(canvas.width, canvas.height/2+(((_y2+_y1)/2)/(_y2-_y1))*canvas.height); 1295 | stage.closePath(); 1296 | stage.stroke(); 1297 | stage.textAlign = "center"; 1298 | stage.textBaseline="top"; 1299 | 1300 | //Draw ticks and numbers on y axis 1301 | stage.lineWidth=1; 1302 | limit = (Math.abs(_x2)>Math.abs(_x1))?Math.abs(_x2):Math.abs(_x1); 1303 | for (i=0; i<=limit; i+=Math.pow(10, Math.floor(Math.log(_x2-_x1) / Math.LN10))/2) { 1304 | if (i===0) continue; 1305 | if (i<=_x2+50) { 1306 | stage.beginPath(); 1307 | stage.moveTo(((i-_x1)/(_x2-_x1))*canvas.width, canvas.height/2+(((_y2+_y1)/2)/(_y2-_y1))*canvas.height-5); 1308 | stage.lineTo(((i-_x1)/(_x2-_x1))*canvas.width, canvas.height/2+(((_y2+_y1)/2)/(_y2-_y1))*canvas.height+5); 1309 | stage.closePath(); 1310 | stage.stroke(); 1311 | stage.fillText(""+(Math.round(i*100)/100), ((i-_x1)/(_x2-_x1))*canvas.width, canvas.height/2+(((_y2+_y1)/2)/(_y2-_y1))*canvas.height+8); 1312 | } 1313 | 1314 | if (i>=_x1-50) { 1315 | stage.beginPath(); 1316 | stage.moveTo(((-i-_x1)/(_x2-_x1))*canvas.width, canvas.height/2+(((_y2+_y1)/2)/(_y2-_y1))*canvas.height-5); 1317 | stage.lineTo(((-i-_x1)/(_x2-_x1))*canvas.width, canvas.height/2+(((_y2+_y1)/2)/(_y2-_y1))*canvas.height+5); 1318 | stage.closePath(); 1319 | stage.stroke(); 1320 | stage.fillText(""+(Math.round(-i*100)/100), ((-i-_x1)/(_x2-_x1))*canvas.width, canvas.height/2+(((_y2+_y1)/2)/(_y2-_y1))*canvas.height+8); 1321 | } 1322 | } 1323 | } 1324 | }.bind(this); 1325 | 1326 | //Updates the canvas 1327 | this.redraw = function() { 1328 | if (points.length>1) { 1329 | stage.fillStyle = "#FFFFFF"; 1330 | stage.fillRect(0, 0, canvas.width, canvas.height); 1331 | graphStage.clearRect(0, 0, canvas.width, canvas.height); 1332 | graphStage.lineCap="round"; 1333 | 1334 | var offsetY = -y1; 1335 | var offsetX = -points[0].x; 1336 | 1337 | drawAxes(x1, x2, y1, y2); 1338 | 1339 | //Draw all the points 1340 | graphStage.strokeStyle="#2980b9"; 1341 | graphStage.lineWidth=1; 1342 | graphStage.beginPath(); 1343 | 1344 | //Find the first point that exists 1345 | var i=0; 1346 | while (isNaN(points[i].y) || points[i].y === undefined || Math.abs(points[i].y) == Infinity) i++; 1347 | graphStage.moveTo(((points[i].x+offsetX)/(points[points.length-1].x-points[0].x))*canvas.width, canvas.height-((points[i].y+offsetY)/(y2-y1))*canvas.height); 1348 | for (i++; icanvas.width*0.8 || Math.abs(mousePos.y-startMouse.y)>canvas.height*0.8) { 1424 | redrawLine(); 1425 | 1426 | //Otherwise, move around the drawing we already have 1427 | } else { 1428 | drawAxes(newx1, newx2, newy1, newy2); 1429 | stage.drawImage(graphCanvas, mousePos.x-startMouse.x, mousePos.y-startMouse.y); 1430 | } 1431 | 1432 | if (event.preventDefault) event.preventDefault(); 1433 | return false; 1434 | 1435 | }.bind(this); 1436 | 1437 | //Stops dragging, resets listeners 1438 | var endDrag = function(event) { 1439 | document.removeEventListener("mousemove", dragMouse, false); 1440 | document.removeEventListener("mouseup", endDrag, false); 1441 | document.documentElement.style["-moz-user-select"] = "auto"; 1442 | document.documentElement.style["-webkit-user-select"] = "auto"; 1443 | document.documentElement.style["-khtml-user-select"] = "auto"; 1444 | document.documentElement.style["user-select"] = "auto"; 1445 | canvas.addEventListener("mouseover", startMouseOver, false); 1446 | canvas.addEventListener("mousemove", moveMouse, false); 1447 | mousePos = getMousePos(event); 1448 | 1449 | var offsetX = ((mousePos.x-startMouse.x)/canvas.width)*(x2-x1); 1450 | var offsetY = ((mousePos.y-startMouse.y)/canvas.height)*(y2-y1); 1451 | this.setRange(x1-offsetX, x2-offsetX, y1+offsetY, y2+offsetY); 1452 | }.bind(this); 1453 | 1454 | var startMouseOver = function(event) { 1455 | canvas.addEventListener("mousemove", moveMouse, false); 1456 | canvas.addEventListener("mouseout", endMouseOver, false); 1457 | }.bind(this); 1458 | 1459 | //Draws coordinates over point 1460 | var moveMouse = function(event) { 1461 | if (distX===0 && distY===0) { 1462 | stage.fillStyle = "#FFFFFF"; 1463 | stage.fillRect(0, 0, canvas.width, canvas.height); 1464 | drawAxes(x1, x2, y1, y2); 1465 | stage.drawImage(graphCanvas, 0, 0); 1466 | mousePos = getMousePos(event); 1467 | if (mousePos.x<0) mousePos.x=0; 1468 | if (mousePos.y<0) mousePos.y=0; 1469 | var offsetY = -y1; 1470 | 1471 | //Check if the function exists at that x value 1472 | var index=-1; 1473 | var dist=-1; 1474 | for (var i=0; i Math.pow(((points[i].x-points[0].x)/(points[points.length-1].x-points[0].x))*canvas.width-mousePos.x, 2) + Math.pow(canvas.height-((points[i].y-y1)/(y2-y1))*canvas.height-mousePos.y, 2)) { 1476 | index = i; 1477 | dist = Math.pow(((points[i].x-points[0].x)/(points[points.length-1].x-points[0].x))*canvas.width-mousePos.x, 2) + Math.pow(canvas.height-((points[i].y-y1)/(y2-y1))*canvas.height-mousePos.y, 2); 1478 | } 1479 | } 1480 | 1481 | if (points[index].y===0 || points[index].y) { 1482 | 1483 | //Draw the coordinate 1484 | stage.fillStyle="#2980b9"; 1485 | stage.beginPath(); 1486 | stage.arc(((points[index].x-points[0].x)/(points[points.length-1].x-points[0].x))*canvas.width, canvas.height-((points[index].y+offsetY)/(y2-y1))*canvas.height, 4, 0, 2*Math.PI); 1487 | stage.closePath(); 1488 | stage.fill(); 1489 | stage.fillStyle="#000"; 1490 | stage.strokeStyle="#FFF"; 1491 | stage.lineWidth=4; 1492 | stage.textBaseline="alphabetic"; 1493 | var txt="(" + (Math.round(points[index].x*100)/100).toFixed(2) + ", " + (Math.round(points[index].y*100)/100).toFixed(2) + ")"; 1494 | 1495 | if (((points[index].x-points[0].x)/(points[points.length-1].x-points[0].x))*canvas.widthcanvas.width-stage.measureText(txt).width/2-2) { 1498 | stage.textAlign = "right"; 1499 | } else { 1500 | stage.textAlign = "center"; 1501 | } 1502 | stage.strokeText(txt, ((points[index].x-points[0].x)/(points[points.length-1].x-points[0].x))*canvas.width, -10+canvas.height-((points[index].y+offsetY)/(y2-y1))*canvas.height); 1503 | stage.fillText(txt, ((points[index].x-points[0].x)/(points[points.length-1].x-points[0].x))*canvas.width, -10+canvas.height-((points[index].y+offsetY)/(y2-y1))*canvas.height); 1504 | } 1505 | } 1506 | }.bind(this); 1507 | 1508 | var endMouseOver = function(event) { 1509 | canvas.removeEventListener("mousemove", moveMouse, false); 1510 | canvas.removeEventListener("mouseout", endMouseOver, false); 1511 | stage.fillStyle = "#FFFFFF"; 1512 | stage.fillRect(0, 0, canvas.width, canvas.height); 1513 | drawAxes(x1, x2, y1, y2); 1514 | stage.drawImage(graphCanvas, 0, 0); 1515 | }.bind(this); 1516 | 1517 | //Zooms based on scroll wheel 1518 | var scrollZoom = function(event) { 1519 | var delta = Math.max(-1, Math.min(1, (event.wheelDelta || -event.detail))); 1520 | distX += delta*(x2-2*distX-x1)/25; 1521 | distY += delta*(y2-2*distY-y1)/25; 1522 | stage.fillStyle = "#FFFFFF"; 1523 | stage.fillRect(0, 0, canvas.width, canvas.height); 1524 | drawAxes(x1 + distX, x2 - distX, y1 + distY, y2 - distY); 1525 | stage.drawImage(graphCanvas, canvas.width*(1-((x2-x1)/(x2-2*distX-x1)))/2, canvas.height*(1-((y2-y1)/(y2-2*distY-y1)))/2, canvas.width*((x2-x1)/(x2-2*distX-x1)), canvas.height*((y2-y1)/(y2-2*distY-y1))); 1526 | if (event.preventDefault) event.preventDefault(); 1527 | clearTimeout(timer); 1528 | timer = setTimeout(updateZoom, 50); 1529 | return false; 1530 | }.bind(this); 1531 | 1532 | var updateZoom = function() { 1533 | stage.fillStyle = "#FFFFFF"; 1534 | stage.fillRect(0, 0, canvas.width, canvas.height); 1535 | this.setRange(x1 + distX, x2 - distX, y1 + distY, y2 - distY); 1536 | distX=0; 1537 | distY=0; 1538 | clearTimeout(timer); 1539 | }.bind(this); 1540 | 1541 | //Returns the canvas element 1542 | this.getCanvas = function() { 1543 | return canvas; 1544 | }; 1545 | 1546 | this.getX1 = function() { 1547 | return x1; 1548 | }; 1549 | 1550 | this.getX2 = function() { 1551 | return x2; 1552 | }; 1553 | 1554 | this.getY1 = function() { 1555 | return y1; 1556 | }; 1557 | 1558 | this.getY2= function() { 1559 | return y2; 1560 | }; 1561 | 1562 | //If canvas drawing is supported 1563 | if (canvas.getContext) { 1564 | 1565 | //Get the canvas context to draw onto 1566 | stage = canvas.getContext("2d"); 1567 | stage.font = "12px sans-serif"; 1568 | canvas.style.backgroundColor="#FFF"; 1569 | 1570 | graphStage = graphCanvas.getContext("2d"); 1571 | 1572 | //Make points 1573 | this.update(); 1574 | 1575 | canvas.addEventListener("mousedown", startDrag, false); 1576 | canvas.addEventListener("mouseover", startMouseOver, false); 1577 | canvas.addEventListener("mousewheel", scrollZoom, false); 1578 | canvas.addEventListener("DOMMouseScroll", scrollZoom, false); 1579 | } else { 1580 | XCalc.log("Canvas not supported in this browser."); 1581 | canvas = document.createElement("div"); 1582 | canvas.innerHTML="Canvas is not supported in this browser."; 1583 | } 1584 | } 1585 | 1586 | var worker={}; 1587 | 1588 | worker.errors=[]; 1589 | 1590 | //logs errors 1591 | worker.log = function(message) { 1592 | this.errors.push(message); 1593 | }; 1594 | 1595 | worker.clearErrors = function() { 1596 | this.errors = []; 1597 | }; 1598 | 1599 | worker.hasErrors = function() { 1600 | return this.errors.length; 1601 | }; 1602 | 1603 | //creates a list of errors to be displayed 1604 | worker.displayErrors = function() { 1605 | var errorDiv = document.createElement("div"); 1606 | errorDiv.className="error"; 1607 | errorDiv.innerHTML = "Errors:"; 1608 | var errorList = document.createElement("ul"); 1609 | for (var i=0; iXCalc.js 2 | Mathematical Expression Calculator by Dave Pagurek. Live demo available at http://www.davepagurek.com/math/. 3 | 4 |

Files

5 | The XCalc release contains XCalc.js and XCalc.min.js. The minified script is smaller and fully functional for use. The uncompressed file is commented and to be used for editing and extending. 6 | 7 |

Usage

8 | Add one of the following to your head tag to start using XCalc: 9 | ```HTML 10 | 11 | ``` 12 | or 13 | ```HTML 14 | 15 | ``` 16 | 17 |

Expressions

18 | Create an XCalc expression like this: 19 | ```javascript 20 | var expression = XCalc.createExpression("(10^2/(2*50))-2(30x)"); 21 | ``` 22 | 23 | To get the result of an expression: 24 | ```javascript 25 | var x=2; 26 | var result = expression.result(x); 27 | ``` 28 | If an x value is not specified, x defaults to zero. 29 | 30 | Expressions can be derived as well: 31 | ```javascript 32 | var x=2; 33 | var rateOfChange = expression.derive().result(x); 34 | ``` 35 | 36 | To get the formula of an expression as a string with HTML that can be formatted nicely (see the example CSS file): 37 | ```javascript 38 | var formulaDiv = document.createElement("div"); 39 | formulaDiv.innerHTML = expression.prettyFormula(); 40 | document.getElementById("resultDiv").appendChild(formulaDiv); 41 | ``` 42 | 43 | To get the formula of an expression as a plaintext string: 44 | ```javascript 45 | var formula = document.createElement("p"); 46 | formula.innerHTML = expression.formula(); 47 | document.getElementById("resultDiv").appendChild(formula); 48 | ``` 49 | 50 | To simplify the formula before getting it: 51 | ```javascript 52 | var formula = document.createElement("p"); 53 | formula.innerHTML = expression.simplify().formula(); 54 | document.getElementById("resultDiv").appendChild(formula); 55 | ``` 56 | 57 |

Graphs

58 | XCalc graphs are created like this: 59 | 60 | ```javascript 61 | var graph = XCalc.graphExpression("x^2"); 62 | ``` 63 | 64 | To add the graph canvas to the page: 65 | ```javascript 66 | document.getElementById("result").appendChild(graph.getCanvas()); 67 | ``` 68 | 69 |

Graph functions

70 |
Constructor
71 | A new graph is created using the following syntax: 72 | ```javascript 73 | var graph = XCalc.graphExpression(expression, width, height, x1, x2, y1, y2); 74 | ``` 75 | 76 | `expression:String` The expression to be graphed. Required. 77 | 78 | `width:Number` The width of the canvas. Default is 400. 79 | 80 | `height:Number` The height of the canvas. Default is 400. 81 | 82 | `x1:Number` and `x2:Number` The horizontal range of the graph, where x1 < x < x2. Defaults to -10 and 10. 83 | 84 | `y1:Number` and `y2:Number` The vertical range of the graph, where y1 < y < y2. Leave both empty or set to the string "auto" for auto range. Defaults to auto range. 85 | 86 |
Range
87 | Set the range of the graph with: 88 | ```javascript 89 | graph.setRange(x1, x2, y1, y2); 90 | ``` 91 | `x1:Number` and `x2:Number` The horizontal range of the graph, where x1 < x < x2. Defaults to -10 and 10. 92 | 93 | `y1:Number` and `y2:Number` The vertical range of the graph, where y1 < y < y2. Leave both empty or set to the string "auto" for auto range. Defaults to auto range. 94 | 95 | You can get the range of the graph with `graph.getX1()` or `graph.getY2()`, which allows you to increment the range of the graph by doing something like this: 96 | ```javascript 97 | graph.setRange(graph.getX1()+5, graph.getX2()-5, graph.getY1()+5, graph.getY2()-5); //zooms in 5px on every side 98 | ``` 99 | 100 |

Error checking

101 | To check if XCalc ran into any errors (such as improperly nested brackets), use the following function: 102 | ```javascript 103 | if (!XCalc.hasErrors()) { 104 | //Carry on as normal 105 | } else { 106 | document.getElementById("errors").appendChild(XCalc.displayErrors()); 107 | XCalc.clearErrors(); 108 | } 109 | ``` 110 | 111 | The `XCalc.displayErrors()` function returns a div with the class "error" with a `
    ` of the errors. 112 | 113 | Make sure to run `XCalc.clearErrors()` to reset `XCalc.hasErrors()`. Otherwise, errors from previous graphs will remain in the list. 114 | 115 |

    Features

    116 |

    Graph Features

    117 |
      118 |
    • Dynamic scale
    • 119 |
    • Auto-range (for y axis)
    • 120 |
    • Displays coordinates on hover
    • 121 |
    • Click and drag graph to pan
    • 122 |
    • Scroll on the graph to zoom in and out
    • 123 |
    124 |

    Operations Supported

    125 | As of version 1.9.3.8: 126 |
      127 |
    • Addition (x+y)
    • 128 |
    • Subtraction (x-y)
    • 129 |
    • Multiplication (x*y or (x)(y))
    • 130 |
    • Division (x/y)
    • 131 |
    • Exponents (x^y or x^(1/y) for roots)
    • 132 |
    • The following functions: 133 |
        134 |
      • sin
      • 135 |
      • cos
      • 136 |
      • tan
      • 137 |
      • asin
      • 138 |
      • acos
      • 139 |
      • atan
      • 140 |
      • abs
      • 141 |
      • log
      • 142 |
      • ln
      • 143 |
      • sqrt
      • 144 |
      145 |
    • 146 |
    • The following constants: 147 |
        148 |
      • e
      • 149 |
      • pi
      • 150 |
      151 |
    • 152 |
    • Single variable evaluation (include "x" in the expression string)
    • 153 |
    154 |

    Algebra and Calculus

    155 |
      156 |
    • Create the derivative of an expression
    • 157 |
    • Simplify an expression
    • 158 |
    159 | --------------------------------------------------------------------------------