fooplot.js 54 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380
  1. /********************************************************************
  2. Online plotter: http://fooplot.com/
  3. Personal website: http://dheera.net/
  4. --------------------- DO NOT REMOVE THIS NOTICE ---------------------
  5. FooPlot embeddable plotter v2.0
  6. Copyright (C) 2012 Dheera Venkatraman <dheera@dheera.net>
  7. This program is free software: you can redistribute it and/or modify
  8. it under the terms of the GNU Lesser General Public License as
  9. published by the Free Software Foundation, either version 3 of the
  10. License, or (at your option) any later version.
  11. This program is distributed in the hope that it will be useful,
  12. but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. GNU Lesser General Public License for more details.
  15. You should have received a copy of the GNU Lesser General Public
  16. License along with this program. If not,
  17. see <http://www.gnu.org/licenses/>.
  18. ---------------------------------------------------------------------
  19. This code is in BETA; some features are incomplete and the code
  20. could be written better. If you are only intending to embed a single
  21. plot into your webpage, it is suggested to use the 'Get Embed Code'
  22. feature on www.fooplot.com to get an IFRAME-based plotter that is
  23. hotlinked to the latest version of this code.
  24. ********************************************************************/
  25. function FooplotSVGRecorder() {
  26. // drop-in replacement for certain features of the relevant features
  27. // of canvas, so that we can record an svg version for generating
  28. // other formats
  29. this.width=null;
  30. this.height=null;
  31. this.font='';
  32. this.textAlign='';
  33. this.svgHeader='';
  34. this.svgBody='';
  35. this.svgFooter='';
  36. this.lineWidth=1;
  37. this.strokeStyle='#000000';
  38. this.fillStyle='#ffffff';
  39. this.x=0;
  40. this.y=0;
  41. this.path_d='';
  42. this.moveToX=null;
  43. this.moveToY=null;
  44. this.clear=function() {
  45. this.svgBody='';
  46. }
  47. this.fillText=function(_text,_x,_y) {
  48. var _textAnchor='start';
  49. if(this.textAlign==='center') _textAnchor='middle';
  50. if(this.textAlign==='right') _textAnchor='end';
  51. this.svgBody+='<text x="'+_x+'" y="'+_y+'" text-anchor="'+_textAnchor+'" style="font:'+this.font+';stroke:none;fill:'+this.fillStyle+'">'+_text+'</text>';
  52. }
  53. this.beginPath=function() {
  54. this.path_d='';
  55. }
  56. this.moveTo=function(_x,_y) {
  57. if(!isNaN(_x) && !isNaN(_y)) {
  58. this.moveToX=_x.toFixed(2);
  59. this.moveToY=_y.toFixed(2);
  60. }
  61. }
  62. this.lineTo=function(_x,_y) {
  63. if(this.moveToX) {
  64. this.path_d+='M'+this.moveToX+' '+this.moveToY+' ';
  65. this.moveToX=null;
  66. this.moveToY=null;
  67. }
  68. this.path_d+='L'+_x.toFixed(2)+' '+_y.toFixed(2)+' ';
  69. }
  70. this.stroke=function() {
  71. this.svgBody+='<path d="'+this.path_d+'" style="fill:none;stroke:'+this.strokeStyle+';stroke-width:'+this.lineWidth+';" />';
  72. this.path_d='';
  73. }
  74. this.fillRect=function(_x,_y,_w,_h) {
  75. this.svgBody+='<rect x="'+_x.toFixed(2)+'" y="'+_y.toFixed(2)+'" width="'+_w.toFixed(2)+'" height="'+_h.toFixed(2)+'" style="fill:'+this.fillStyle+';stroke:none;" />';
  76. }
  77. this.getSVG=function() {
  78. var _svg='';
  79. _svg+='<?xml version="1.0" standalone="no"?>';
  80. _svg+='<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">';
  81. _svg+='<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 '+this.width+' '+this.height+'" version="1.1">';
  82. _svg+='<clipPath id="box"><rect x="0" y="0" width="'+this.width+'" height="'+this.height+'" style="fill:none;stroke:none;" /></clipPath>';
  83. _svg+='<g clip-path="url(#box)">';
  84. _svg+=this.svgBody;
  85. _svg+='</g></svg>';
  86. return _svg;
  87. }
  88. }
  89. function Fooplot(container,options) {
  90. // PRIVATE: INITIALISATION AND BASIC FUNCTIONS
  91. FOOPLOT_INSTANCES.push(this);
  92. this.container=container;
  93. this.container.style.overflow='hidden';
  94. this.container.style.position='relative';
  95. this.container.style.webkitUserSelect='none';
  96. this.container.style.MozUserSelect='none';
  97. this.container.style.userSelect='none';
  98. if(FOOPLOT_MSIE) this.container.unselectable=true;
  99. this.container.style.cursor='move';
  100. this.cover=document.createElement('div');
  101. this.cover.style.position='absolute';
  102. this.cover.style.width='100%';
  103. this.cover.style.height='100%';
  104. this.container.style.webkitUserSelect='none';
  105. this.container.style.MozUserSelect='none';
  106. if(FOOPLOT_MSIE) this.cover.unselectable=true;
  107. this.cover.style.zIndex=100;
  108. this.cover.style.background='#ffffff';
  109. this.cover.style.filter='alpha(opacity=0)';
  110. this.cover.style.opacity=0;
  111. this.container.appendChild(this.cover);
  112. this.subcontainer=document.createElement('div');
  113. this.subcontainer.style.position='absolute';
  114. this.subcontainer.style.zIndex='1';
  115. this.subcontainer.style.webkitUserSelect='none';
  116. this.subcontainer.style.userSelect='none';
  117. if(FOOPLOT_MSIE) this.subcontainer.unselectable=true;
  118. this.container.appendChild(this.subcontainer);
  119. this.recorder=new FooplotSVGRecorder();
  120. this.toolcontainer=document.createElement('div');
  121. this.toolcontainer.style.position='absolute';
  122. this.toolcontainer.style.top='100%';
  123. this.toolcontainer.style.zIndex='200';
  124. this.toolcontainer.style.opacity=0.7;
  125. this.toolcontainer.style.marginTop='-60px';
  126. this.toolcontainer.style.marginLeft='10px';
  127. this.toolcontainer.style.padding='10px';
  128. this.toolcontainer.style.height='32px';
  129. this.toolcontainer.style.webkitBorderRadius='5px';
  130. this.toolcontainer.style.visibility='hidden';
  131. this.container.appendChild(this.toolcontainer);
  132. this.addToolSeparator=function() {
  133. var newtool=document.createElement('div');
  134. newtool.style.display='inline';
  135. newtool.style.width='32px';
  136. newtool.style.height='1px';
  137. newtool.style.border='0px';
  138. newtool.style.padding='0px';
  139. newtool.style.marginRight='15px';
  140. this.toolcontainer.appendChild(newtool);
  141. }
  142. this.addToolButton=function(image,action,modeId,tooltip) {
  143. var newtool=document.createElement('button');
  144. newtool.className='fooplot-tool';
  145. newtool.style.width='32px';
  146. if(tooltip) newtool.title=tooltip;
  147. newtool.style.background=image;
  148. newtool.style.position='relative';
  149. newtool.style.height='32px';
  150. newtool.style.border='0px';
  151. newtool.style.padding='0px';
  152. newtool.style.marginRight='15px';
  153. newtool.style.cursor='pointer';
  154. if(!modeId) {
  155. newtool.onmousedown=function() {this.style.opacity=0.7;this.style.filter='alpha(opacity=70)';}
  156. newtool.onmouseup=function() {this.style.opacity=1;this.style.filter='';}
  157. newtool.onmouseout=function() {this.style.opacity=1;this.style.filter='';}
  158. newtool.onclick=action;
  159. } else {
  160. newtool.onclick=function() {
  161. for (i in FOOPLOT_INSTANCES) if(this.parentNode===FOOPLOT_INSTANCES[i].toolcontainer) var _self=FOOPLOT_INSTANCES[i];
  162. _self.selectMode(this);
  163. }
  164. this.toolsMode.push({'tool':newtool,'id':modeId});
  165. }
  166. this.toolcontainer.appendChild(newtool);
  167. return newtool;
  168. }
  169. this.hideIntersection=function() {
  170. this.intersectionPoint.style.visibility='hidden';
  171. this.intersectionDisplay.style.visibility='hidden';
  172. }
  173. this.hideTrace=function() {
  174. this.tracePoint.style.visibility='hidden';
  175. this.traceDisplay.style.visibility='hidden';
  176. }
  177. this.toolsMode=[];
  178. this.selectedMode=FOOPLOT_MODE_MOVE;
  179. this.selectMode=function(obj) {
  180. this.hideIntersection();
  181. this.hideTrace();
  182. for (i in this.toolsMode) {
  183. if(this.toolsMode[i].tool===obj || this.toolsMode[i].id===obj) {
  184. this.selectedMode=this.toolsMode[i].id;
  185. this.toolsMode[i].tool.style.opacity=0.7;
  186. this.toolsMode[i].tool.style.filter='alpha(opacity=70)';
  187. } else {
  188. this.toolsMode[i].tool.style.opacity=1;
  189. this.toolsMode[i].tool.style.filter='';
  190. }
  191. }
  192. }
  193. this.zoomTimeout=null;
  194. this.zoomSelf=null;
  195. this.zoomPendingFactor=1;
  196. this.zoom=function(factor) {
  197. if(this.zoomTimeout) window.clearTimeout(this.zoomTimeout);
  198. if(FOOPLOT_TRANSITIONS && this.canvas && this.canvas.style && factor!=1) {
  199. this.canvas.style.OTransition='-o-transform 0.4s ease';
  200. this.canvas.style.webkitTransition='-webkit-transform 0.4s ease';
  201. this.canvas.style.MozTransition='-moz-transform 0.4s ease';
  202. this.canvas.style.msTransition='-webkit-transform 0.4s ease';
  203. this.canvas.style.webkitTransform+='scale('+factor+')';
  204. this.canvas.style.MozTransform+='scale('+factor+')';
  205. this.canvas.style.msTransform+='scale('+factor+')';
  206. this.canvas.style.OTransform+='scale('+factor+')';
  207. }
  208. this.hideIntersection();
  209. this.hideTrace();
  210. this.zoomSelf=this;
  211. this.zoomPendingFactor*=factor;
  212. this.zoomTimeout=window.setTimeout(function(_self) {
  213. if(!_self) for (i in FOOPLOT_INSTANCES) if(FOOPLOT_INSTANCES[i].zoomSelf) var _self=FOOPLOT_INSTANCES[i].zoomSelf;
  214. var centerx=(_self.xmax+_self.xmin)/2;
  215. var centery=(_self.ymax+_self.ymin)/2;
  216. _self.xmax=(_self.xmax-centerx)/_self.zoomPendingFactor+centerx;
  217. _self.xmin=(_self.xmin-centerx)/_self.zoomPendingFactor+centerx;
  218. _self.ymax=(_self.ymax-centery)/_self.zoomPendingFactor+centery;
  219. _self.ymin=(_self.ymin-centery)/_self.zoomPendingFactor+centery;
  220. _self.zoomPendingFactor=1;
  221. _self.reDraw();
  222. _self.canvas.style.OTransition='color 0 ease'; // opera hack
  223. _self.canvas.style.webkitTransition='';
  224. _self.canvas.style.MozTransition='';
  225. _self.canvas.style.msTransition='';
  226. _self.canvas.style.webkitTransform='';
  227. _self.canvas.style.MozTransform='';
  228. _self.canvas.style.msTransform+='';
  229. _self.canvas.style.OTransform='';
  230. _self.onWindowChange([_self.xmin,_self.xmax,_self.ymin,_self.ymax]);
  231. _self.zoomTimeout=null;
  232. _self.zoomSelf=null;
  233. },FOOPLOT_TRANSITIONS?450:0,this);
  234. }
  235. this.toolZoomIn=this.addToolButton(
  236. 'url(\'data:image/gif;base64,R0lGODlhIAAgAPZRAP9/AP+AAP+AAf+AAv+BA/+BBP+DB/+GDf+GDv+HD/+KFv+LGP+MGv+OHf+PH/+QIv+UKf+UKv+YMv+aNv+bN/+gQf+iRf+jSP+kSf+nUP+oUv+pU/+pVP+qVv+sWf+wYf+xY/+2bv+5c/+8ev+9fP+/gP/Bg//Chv/Eiv/Gjf/HkP/Jk//Ll//Mmf/Onv/Qof/Ysv/bt//cuf/cuv/du//evf/evv/gwf/gwv/hw//jx//kyv/mzf/nz//s2v/v3//v4P/w4f/w4v/x4//y5v/06v/27f/48f/59P/69v/79//7+P/8+f/8+v/9+//+/f/+/v///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAFIALAAAAAAgACAAAAf+gFJSAAEAhoeIiYqJglKFi5CRhwGDkpaSj5eak5oGDJuaFio6SFFGMycQoIoJLlGvsK9IIpmgFECxua85CqsJQ7lAP0S5MKsvuUuGFLofmxi6ygDMuUe9ly3Ry7pRHpo8uk3buimXBk2vNQnrCYYG7AmlUTmXD7AytYpLr0OXAfIyDAg0YCjAQAPyYmjKoW0atxOaVDSklguDJgnoYkmjCAtIO00lko2LVQGUAXCxePTwoUvDqgY1uIVzCSpACHm5hoyQ16TDKgAOQLDYUaQGig7tMOyL0oTmT0hKX/V8GgkDT1VUF0UtkbUqCUX5uiIKKxaRo7KJKAkihLaQoEAAOw==\')',
  237. function() {
  238. for (i in FOOPLOT_INSTANCES) if(this.parentNode===FOOPLOT_INSTANCES[i].toolcontainer) var _self=FOOPLOT_INSTANCES[i];
  239. _self.zoom(2);
  240. },
  241. null,
  242. 'Zoom In'
  243. );
  244. this.toolZoomOut=this.addToolButton(
  245. 'url(\'data:image/gif;base64,R0lGODlhIAAgAPZLAP9/AP+AAP+AAf+AAv+BBP+DB/+GDf+GDv+HD/+KFv+LF/+LGP+MGv+OHf+PH/+QIv+UKf+UKv+YMv+bN/+gQf+iRf+jSP+kSf+nUP+oUv+pU/+pVP+qVv+rWP+sWf+wYf+xY/+zZ/+2bv+5c/+8ev+9fP+/gP/Bg//Chv/Eiv/Gjf/HkP/Jk//Ll//Mmf/Onv/Qof/Ysv/bt//cuv/evf/evv/gwf/gwv/hw//jx//kyv/mzf/v4P/w4f/w4v/x4//06v/27f/48f/59P/69v/79//7+P/8+v/9+//+/f/+/v///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAEwALAAAAAAgACAAAAfXgExMAAEAhoeIiYqJgkyFi5CRhwGDkpaSj5eak5oFDJuaFSw5Q0tBMygQoIoIL0uvsK9DI5mgEzyxua84CqsIP7rBMaswwcYfmxfGxkILmi7Lxh2aO9HBKpcFR7khCt7exbE2lw+604gtuT+XAaWx54fpsTKaOda6KJoq97kXmhL8YPFAsKlEwCUUQBWoxi/DqgY1+B1xCCqACHfqSLg7wmEVgAcfWugAUiMFB4IXjLya6FFSypUdW0K6sFGVzEUvTdyMdKHEzlW1fipyJDQRJUFFDRUSFAgAOw==\')',
  246. function() {
  247. for (i in FOOPLOT_INSTANCES) if(this.parentNode===FOOPLOT_INSTANCES[i].toolcontainer) var _self=FOOPLOT_INSTANCES[i];
  248. _self.zoom(0.5);
  249. },
  250. null,
  251. 'Zoom Out'
  252. );
  253. this.addToolSeparator();
  254. this.toolMove=this.addToolButton(
  255. 'url(\'data:image/gif;base64,R0lGODlhIAAgAPY/AP9/AP+AAP+AAf+AAv+BBP+CBf+FDP+GDv+HD/+IEf+IEv+JE/+KFv+MGv+NG/+OHf+PH/+PIP+QIf+QIv+TKP+UKf+bOP+dPP+fQP+jSP+oUv+pVP+rWP+uXf+xY/+1a/+3cP+6dv+7d/+7eP+8ef+9e/++ff/Dh//DiP/Hj//Ikv/Klf/Ll//LmP/NnP/Pn//du//fv//hw//ixv/kyv/lzP/mzv/o0f/o0v/p1P/t2//u3v/v4P/w4f/+/v///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAEAALAAAAAAgACAAAAf0gEBAAAEAhoeIiYqHhYJAhYuRkoyDk4mQloaYlhw5D5mEoAAjPz87E6KWBC6lpaepkQgwra09qLCJDhYWIKUiGhsXuJEZpRbDk8U/x8jExs0TwonKzIgUm4sRPBuK1IogLJMPOz8a3c+JvSiRDjq+u/C8pR7xuymlJIoBOLT9/v+lPijqALDgPxmLTPjKwLAhqR8gGjZsUYoGgkisMJxb9u2HjQWRAhCAoXEaOkQgcICchCDCxmqHMjBo5q3Zopo2TXLMaSgDCHClWIwYUSEngxv+VGAbpsAGrXU8DSGYgU/U0kUIYoCIqg/WI66UBIXi2QhIIAA7\')',
  256. null,FOOPLOT_MODE_MOVE,'Move');
  257. this.toolZoomBox=this.addToolButton(
  258. 'url(\'data:image/gif;base64,R0lGODlhIAAgAPYAAP9/AP+AAP+AAf+AAv+BA/+BBP+CBf+DB/+GDf+GDv+HD/+KFv+LGP+MGv+OHf+PH/+QIv+TJ/+UKf+UKv+YMv+bN/+gQf+iRf+jSP+kSf+nT/+nUP+oUv+pU/+pVP+qVv+sWf+uXv+wYf+xY/+1a/+2bv+3b/+5c/+8ev+9fP+/gP/Bg//Chv/Eiv/Gjf/Hj//HkP/Jk//JlP/Ll//Mmf/Onv/Qof/Xr//Ysv/bt//cuv/evf/evv/gwf/gwv/hw//jx//kyv/mzf/r1//v4P/w4f/w4v/x4//06v/27f/48f/59P/69v/79//7+P/8+v/9+//9/P/+/f/+/v///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAFUALAAAAAAgACAAAAf+gFVVAAEAhoeIiYqHhYJVhYuRkoyDk5aTkJeajJoHDZuaFzFAS1RJOiwSoIoKNVSvsK9LJ5mgFUSxua8/C6sKR09LT03CS07Gx1Q4qzZUGoaQAdKHL1Qimxmvz4qZ1Uq9lzTahgfl5geG1VQfmkLjADe5JumvL5cHT+/xsfMA6j+XIMDatg9WP3VELgUo5cxQwVcHX+nQ5OPdjCEYMYagR2WFJnXbJIHUROGdSCpEFGxKQYVEhJcwY0ZoZgHUAXe6cnJY5WBHTl1Pdq4ywTAXERQMg64C8GDEjCBIdrT4oDKDk1dP2C2VZBWr0K2Lur5SBXYRhqsqykrCkELtqloObhU9ipsogCO60AAICgQAOw==\')',
  259. null,FOOPLOT_MODE_ZOOMBOX,'Zoom Box');
  260. this.toolTrace=this.addToolButton(
  261. 'url(\'data:image/gif;base64,R0lGODlhIAAgAMYAAP9/AP+AAP+AAf+AAv+BBP+CBf+FDP+GDv+HD/+IEf+IEv+JE/+KFv+MGv+NG/+OHf+PH/+PIP+QIf+QIv+TKP+UKf+bOP+dPP+fQP+jSP+oUv+pVP+rWP+uXf+xY/+1a/+3cP+6dv+7d/+7eP+8ef+9e/++ff/Dh//DiP/Hj//Ikv/Klf/Ll//LmP/NnP/Pn//du//fv//hw//ixv/kyv/lzP/mzv/o0f/o0v/p1P/t2//u3v/v4P/w4f/+/v////9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/ACH5BAEAAD4ALAAAAAAgACAAAAdfgD4+AAEAhoeIiYqHhYI+hYuRkoyDk5aTkJeajJudnp+goaKjpKWmp6iJP6urqD+qr6SxirOgtbSsnre4rZu7vL+jrL2ph8GmxMUAw8rNzs/Q0YqZ0qePxQGOhKaNPoEAOw==\')',
  262. null,FOOPLOT_MODE_TRACE,'Trace');
  263. this.toolIntersection=this.addToolButton(
  264. 'url(\'data:image/gif;base64,R0lGODlhIAAgAMYAAP9/AP+AAP+AAf+AAv+BBP+CBf+FDP+GDv+HD/+IEf+IEv+JE/+KFv+MGv+NG/+OHf+PH/+PIP+QIf+QIv+TKP+UKf+bOP+dPP+fQP+jSP+oUv+pVP+rWP+uXf+xY/+1a/+3cP+6dv+7d/+7eP+8ef+9e/++ff/Dh//DiP/Hj//Ikv/Klf/Ll//LmP/NnP/Pn//du//fv//hw//ixv/kyv/lzP/mzv/o0f/o0v/p1P/t2//u3v/v4P/w4f/+/v////9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/AP9/ACH5BAEAAD4ALAAAAAAgACAAAAdygD4+AAEAhoeIiYqHhYI+hYuRkoyDk5aTkJeajJuRP52gn6CaP6Kjlqank6mqi6ytiq+opaWeo6yyspK5ibq7trS+tp7BsLHFxojBwqe0yb2HzJ24z7ywvrXNl9mb0tHe3rfP44uZ5OeLj+gBjoTPjT6BADs=\')',
  265. null,FOOPLOT_MODE_INTERSECTION,'Find Intersections and Roots');
  266. this.zoomboxBox=document.createElement('div');
  267. this.zoomboxBox.style.position='absolute';
  268. this.zoomboxBox.style.visibility='hidden';
  269. this.zoomboxBox.style.width='1px';
  270. this.zoomboxBox.style.height='1px';
  271. this.zoomboxBox.style.top='-1px';
  272. this.zoomboxBox.style.left='-1px';
  273. this.zoomboxBox.style.border='1px solid #ff8000';
  274. this.zoomboxBox.style.background='#ffa000';
  275. this.zoomboxBox.style.opacity=0.5;
  276. this.zoomboxBox.style.filter='alpha(opacity=50)';
  277. this.zoomboxBox.style.zIndex=50;
  278. this.container.appendChild(this.zoomboxBox);
  279. this.tracePoint=document.createElement('div');
  280. this.tracePoint.style.position='absolute';
  281. this.tracePoint.style.visibility='hidden';
  282. this.tracePoint.style.width='5px';
  283. this.tracePoint.style.height='5px';
  284. this.tracePoint.style.top='-1px';
  285. this.tracePoint.style.left='-1px';
  286. this.tracePoint.style.border='1px solid #ffffff';
  287. this.tracePoint.style.background='#ff8000';
  288. this.tracePoint.style.zIndex=50;
  289. this.container.appendChild(this.tracePoint);
  290. this.traceDisplay=document.createElement('div');
  291. this.traceDisplayText=document.createTextNode('');
  292. this.traceDisplay.style.position='absolute';
  293. this.traceDisplay.style.visibility='hidden';
  294. this.traceDisplay.style.height='20px';
  295. this.traceDisplay.style.padding='4px';
  296. this.traceDisplay.style.top='-1px';
  297. this.traceDisplay.style.left='-1px';
  298. this.traceDisplay.style.border='1px solid #a0a0a0';
  299. this.traceDisplay.style.background='#ffffff';
  300. this.traceDisplay.style.webkitBoxShadow='5px 5px 5px #808080';
  301. this.traceDisplay.style.MozBoxShadow='5px 5px 5px #808080';
  302. this.traceDisplay.style.OBoxShadow='5px 5px 5px #808080';
  303. this.traceDisplay.style.msBoxShadow='5px 5px 5px #808080';
  304. this.traceDisplay.style.boxShadow='5px 5px 5px #808080';
  305. this.traceDisplay.style.zIndex=50;
  306. this.traceDisplay.appendChild(this.traceDisplayText);
  307. this.container.appendChild(this.traceDisplay);
  308. this.intersectionPoint=document.createElement('div');
  309. this.intersectionPoint.style.position='absolute';
  310. this.intersectionPoint.style.visibility='hidden';
  311. this.intersectionPoint.style.width='5px';
  312. this.intersectionPoint.style.height='5px';
  313. this.intersectionPoint.style.top='-1px';
  314. this.intersectionPoint.style.left='-1px';
  315. this.intersectionPoint.style.border='1px solid #ffffff';
  316. this.intersectionPoint.style.background='#ff8000';
  317. this.intersectionPoint.style.zIndex=50;
  318. this.container.appendChild(this.intersectionPoint);
  319. this.intersectionDisplay=document.createElement('div');
  320. this.intersectionDisplayText=document.createTextNode('');
  321. this.intersectionDisplay.style.position='absolute';
  322. this.intersectionDisplay.style.visibility='hidden';
  323. this.intersectionDisplay.style.height='20px';
  324. this.intersectionDisplay.style.padding='4px';
  325. this.intersectionDisplay.style.top='-1px';
  326. this.intersectionDisplay.style.left='-1px';
  327. this.intersectionDisplay.style.border='1px solid #a0a0a0';
  328. this.intersectionDisplay.style.background='#ffffff';
  329. this.intersectionDisplay.style.webkitBoxShadow='5px 5px 5px #808080';
  330. this.intersectionDisplay.style.MozBoxShadow='5px 5px 5px #808080';
  331. this.intersectionDisplay.style.OBoxShadow='5px 5px 5px #808080';
  332. this.intersectionDisplay.style.msBoxShadow='5px 5px 5px #808080';
  333. this.intersectionDisplay.style.boxShadow='5px 5px 5px #808080';
  334. this.intersectionDisplay.style.zIndex=50;
  335. this.intersectionDisplay.appendChild(this.intersectionDisplayText);
  336. this.container.appendChild(this.intersectionDisplay);
  337. this.selectMode(FOOPLOT_MODE_MOVE);
  338. this.canvas=false;
  339. this.context=false;
  340. this.vars={'pi':3.14159265358979323,'e':2.718281828459045,'s':0,'t':0,'x':0,'theta':0};
  341. this.plots=[];
  342. this.plotlastid=0;
  343. this.width=false;
  344. this.height=false;
  345. // window options
  346. this.xmin=-6.5;
  347. this.xmax=6.5;
  348. this.ymin=-4;
  349. this.ymax=4;
  350. this.xgrid=''; // blank means auto
  351. this.ygrid=''; // blank means auto
  352. this.xgridunits=null;
  353. this.ygridunits=null;
  354. this.showGrid=true;
  355. this.showAxes=true;
  356. this.showTicks=true;
  357. this.showLabels=true;
  358. this.gridColor='#D0D0D0';
  359. this.axesColor='#606060';
  360. this.labelsColor='#606060';
  361. this.backgroundColor='#FFFFFF';
  362. this.setSize=function() {
  363. this.width=this.container.offsetWidth;
  364. this.height=this.container.offsetHeight;
  365. this.canvas.setAttribute('width',this.width);
  366. this.canvas.setAttribute('height',this.height);
  367. this.recorder.width=this.width;
  368. this.recorder.height=this.height;
  369. }
  370. this.canvas=document.createElement('canvas');
  371. try { if(FOOPLOT_MSIE) {
  372. G_vmlCanvasManager.initElement(this.canvas);
  373. } } catch(error) { }
  374. this.subcontainer.appendChild(this.canvas);
  375. this.setSize();
  376. this.canvas.style.webkitBackfaceVisibility='hidden';
  377. this.canvas.style.webkitTransform='translate3d(0,0,0)';
  378. this.canvas.style.MozTransform='translate3d(0,0,0)';
  379. this.context=this.canvas.getContext("2d");
  380. this.getRealGrid=function() {
  381. if(parseFloat(this.xgrid)) {
  382. realxgrid=this.xgrid;
  383. } else {
  384. orderfull=-0.9+Math.log(this.xmax-this.xmin)/Math.log(10);
  385. order=Math.floor(orderfull);
  386. rem=orderfull-order;
  387. realxgrid=Math.pow(10,order);
  388. if(rem>.7) realxgrid*=5;
  389. else if(rem>.3) realxgrid*=2;
  390. }
  391. if(parseFloat(this.ygrid)) {
  392. realygrid=this.ygrid;
  393. } else {
  394. orderfull=-0.9+Math.log(this.width/this.height*(this.ymax-this.ymin))/Math.log(10);
  395. order=Math.floor(orderfull);
  396. rem=orderfull-order;
  397. realygrid=Math.pow(10,order);
  398. if(rem>.7) realygrid*=5;
  399. else if(rem>.3) realygrid*=2;
  400. }
  401. return [realxgrid,realygrid];
  402. }
  403. this.drawGrid=function() {
  404. var px,py,x,y,realxgrid,realygrid,order,orderfull,g;
  405. g=this.getRealGrid();
  406. realxgrid=g[0];
  407. realygrid=g[1];
  408. if((this.ymax-this.ymin)/realygrid>this.height/2 || (this.xmax-this.xmin)/realxgrid>this.width/2) {
  409. this.context.fillRect(0,0,this.width,this.height);
  410. } else {
  411. this.context.beginPath();
  412. for(y=Math.ceil(this.ymin/realygrid)*realygrid;y<=this.ymax;y+=realygrid) {
  413. py=(1-(y-this.ymin)/(this.ymax-this.ymin))*this.height;
  414. this.context.moveTo(0,Math.floor(py));
  415. this.context.lineTo(this.width,Math.floor(py));
  416. }
  417. for(x=Math.ceil(this.xmin/realxgrid)*realxgrid;x<=this.xmax;x+=realxgrid) {
  418. px=(x-this.xmin)/(this.xmax-this.xmin)*this.width;
  419. this.context.moveTo(Math.floor(px),0);
  420. this.context.lineTo(Math.floor(px),this.height);
  421. }
  422. this.context.stroke();
  423. }
  424. }
  425. this.drawLabels=function() {
  426. var px,py,x,y,realxgrid,realygrid;
  427. g=this.getRealGrid();
  428. realxgrid=g[0];
  429. realygrid=g[1];
  430. var orderx=Math.pow(10,2-Math.floor(Math.log(this.xmax-this.xmin)/Math.log(10)));
  431. var ordery=Math.pow(10,2-Math.floor(Math.log(this.ymax-this.ymin)/Math.log(10)));
  432. this.context.font='10px Droid Sans,Trebuchet MS,Arial,Helvetica,sans-serif';
  433. px=(0-this.xmin)/(this.xmax-this.xmin)*this.width;
  434. this.context.textAlign='left';
  435. if(px<0) px=0;
  436. if(px>this.width-this.width/80-16) {
  437. if(this.xmax>0) px-=20;
  438. else px=this.width-this.width/100-18;
  439. this.context.textAlign='right';
  440. }
  441. if((this.ymax-this.ymin)/realygrid<this.height/6) {
  442. for(y=Math.floor(this.ymin/realygrid)*realygrid;y<=this.ymax;y+=realygrid) {
  443. py=(this.ymax-y)/(this.ymax-this.ymin)*this.height;
  444. if(this.ygridunits===FOOPLOT_UNITS_PI) {
  445. printy=this.tryToMakeFraction(y/this.vars.pi)+'π';
  446. } else if(this.ygridunits===FOOPLOT_UNITS_E) {
  447. printy=this.tryToMakeFraction(y/this.vars.e)+'e';
  448. } else {
  449. printy=parseFloat(Math.round(y*ordery)/ordery);
  450. }
  451. if(py>8 && py<this.height-8) this.context.fillText(printy,px+this.width/80,py+2.5);
  452. }
  453. }
  454. this.context.textAlign='center';
  455. py=(this.ymax)/(this.ymax-this.ymin)*this.height;
  456. if(py<0) py=0;
  457. if(py>this.height-16-this.width/80) {
  458. if(this.ymin<0) py-=22;
  459. else py=this.height-20-this.width/100;
  460. }
  461. if((this.xmax-this.xmin)/realxgrid<this.width/6) {
  462. for(x=Math.floor(this.xmin/realxgrid)*realxgrid;x<=this.xmax;x+=realxgrid) {
  463. px=(x-this.xmin)/(this.xmax-this.xmin)*this.width;
  464. if(this.xgridunits===FOOPLOT_UNITS_PI) {
  465. printx=this.tryToMakeFraction(x/this.vars.pi)+'π';
  466. } else if(this.xgridunits===FOOPLOT_UNITS_E) {
  467. printx=this.tryToMakeFraction(x/this.vars.e)+'e';
  468. } else {
  469. printx=parseFloat(Math.round(x*orderx)/orderx);
  470. }
  471. if(px>8 && px<this.width-8) this.context.fillText(printx,px,py+this.width/80+8);
  472. }
  473. }
  474. }
  475. this.drawAxes=function() {
  476. var px,py,x,y,realxgrid,realygrid;
  477. g=this.getRealGrid();
  478. realxgrid=g[0];
  479. realygrid=g[1];
  480. if(this.xmin<0 && this.xmax>0) {
  481. px=(0-this.xmin)/(this.xmax-this.xmin)*this.width;
  482. } else if(this.xmin>=0) {
  483. px=0;
  484. } else if(this.xmax<=0) {
  485. px=this.width;
  486. }
  487. this.context.beginPath();
  488. this.context.moveTo(px,0);
  489. this.context.lineTo(px,this.height);
  490. this.context.stroke();
  491. if(this.showTicks) {
  492. if((this.ymax-this.ymin)/realygrid>this.height/2) {
  493. this.context.fillRect(px-this.width/100,0,this.width/50,this.height);
  494. } else {
  495. this.context.beginPath();
  496. for(y=Math.floor(this.ymin/realygrid)*realygrid;y<=this.ymax;y+=realygrid) {
  497. py=(this.ymax-y)/(this.ymax-this.ymin)*this.height;
  498. this.context.moveTo(px-this.width/100,py);
  499. this.context.lineTo(px+this.width/100,py);
  500. }
  501. this.context.stroke();
  502. }
  503. }
  504. if(this.ymin<0 && this.ymax>0) {
  505. py=(1-(0-this.ymin)/(this.ymax-this.ymin))*this.height;
  506. } else if(this.ymin>=0) {
  507. py=this.height;
  508. } else if(this.ymax<=0) {
  509. py=0;
  510. }
  511. this.context.beginPath();
  512. this.context.moveTo(0,py);
  513. this.context.lineTo(this.width,py);
  514. this.context.stroke();
  515. if(this.showTicks) {
  516. if((this.xmax-this.xmin)/realxgrid>this.width/2) {
  517. this.context.fillRect(0,py-this.width/100,this.width,this.width/50);
  518. } else {
  519. this.context.beginPath();
  520. for(x=Math.floor(this.xmin/realxgrid)*realxgrid;x<=this.xmax;x+=realxgrid) {
  521. px=(x-this.xmin)/(this.xmax-this.xmin)*this.width;
  522. this.context.moveTo(px,py-this.width/100);
  523. this.context.lineTo(px,py+this.width/100);
  524. }
  525. this.context.stroke();
  526. }
  527. }
  528. }
  529. this.clear=function() {
  530. if(this.context!=this.recorder) {
  531. this.canvas.width=this.canvas.width;
  532. }
  533. if(this.context.clear) {
  534. this.context.clear();
  535. }
  536. this.context.fillStyle=this.backgroundColor;
  537. this.context.fillRect(0,0,this.width,this.height);
  538. }
  539. // PRIVATE: EVENT HANDLERS
  540. this.isMouseDown=0;
  541. this.dragInitX=0;
  542. this.dragInitY=0;
  543. this.dpx=0;
  544. this.dpy=0;
  545. this.container.onmousemove=function(e) {
  546. if(e === null) e = window.event;
  547. for (i in FOOPLOT_INSTANCES) if(this===FOOPLOT_INSTANCES[i].container) var _self=FOOPLOT_INSTANCES[i];
  548. if(e && e.preventDefault) e.preventDefault();
  549. if(e && (e.srcElement?e.srcElement:e.target).className==='fooplot-tool') return null;
  550. if(_self.zoomTimeout) return null;
  551. var offx=_self.container.offsetLeft;
  552. var offy=_self.container.offsetTop;
  553. if(_self.container.parentNode) {
  554. offx+=_self.container.parentNode.offsetLeft;
  555. offy+=_self.container.parentNode.offsetTop;
  556. }
  557. var mx=(!FOOPLOT_MSIE?e.pageX:(document.body.scrollLeft+event.clientX))-offx;
  558. var my=(!FOOPLOT_MSIE?e.pageY:(document.body.scrollTop+event.clientY))-offy;
  559. if(_self.isMouseDown) {
  560. _self.dpx=mx-_self.dragInitX;
  561. _self.dpy=my-_self.dragInitY;
  562. switch(_self.selectedMode) {
  563. case FOOPLOT_MODE_MOVE:
  564. _self.subcontainer.style.left=_self.dpx+'px';
  565. _self.subcontainer.style.top=_self.dpy+'px';
  566. break;
  567. case FOOPLOT_MODE_ZOOMBOX:
  568. _self.zoomboxBox.style.width=(_self.dpx)+'px';
  569. _self.zoomboxBox.style.height=(_self.dpy)+'px';
  570. break;
  571. case FOOPLOT_MODE_TRACE:
  572. var initx=mx/_self.width*(_self.xmax-_self.xmin)+_self.xmin;
  573. var order=Math.pow(10,2-Math.floor(Math.log(_self.xmax-_self.xmin)/Math.log(10)));
  574. var initx=parseFloat(Math.floor(initx*order)/order);
  575. var inity=(1-my/_self.height)*(_self.ymax-_self.ymin)+_self.ymin;
  576. _self.showTrace(initx,inity);
  577. break;
  578. }
  579. }
  580. }
  581. this.container.onmouseover=function(e) {
  582. if(e === null) e = window.event;
  583. for (i in FOOPLOT_INSTANCES) if(this===FOOPLOT_INSTANCES[i].container) var _self=FOOPLOT_INSTANCES[i];
  584. if(e && e.preventDefault) e.preventDefault();
  585. _self.toolcontainer.style.visibility='visible';
  586. if(e && (e.srcElement?e.srcElement:e.target).className==='fooplot-tool') return null;
  587. }
  588. this.container.onmouseout=function(e) {
  589. if(e === null) e = window.event;
  590. for (i in FOOPLOT_INSTANCES) if(this===FOOPLOT_INSTANCES[i].container) var _self=FOOPLOT_INSTANCES[i];
  591. if(e && e.preventDefault) e.preventDefault();
  592. _self.toolcontainer.style.visibility='hidden';
  593. if(e && (e.srcElement?e.srcElement:e.target).className=='fooplot-tool') return null;
  594. this.onmouseup(e);
  595. }
  596. this.container.onmousedown=function(e) {
  597. if(e === null) e = window.event;
  598. for (i in FOOPLOT_INSTANCES) if(this===FOOPLOT_INSTANCES[i].container) var _self=FOOPLOT_INSTANCES[i];
  599. if(e && e.preventDefault) e.preventDefault();
  600. if(e && (e.srcElement?e.srcElement:e.target).className=='fooplot-tool') return null;
  601. if(_self.zoomTimeout) return null;
  602. var offx=_self.container.offsetLeft;
  603. var offy=_self.container.offsetTop;
  604. if(_self.container.parentNode) {
  605. offx+=_self.container.parentNode.offsetLeft;
  606. offy+=_self.container.parentNode.offsetTop;
  607. }
  608. var mx=(!FOOPLOT_MSIE?e.pageX:(document.body.scrollLeft+event.clientX))-offx;
  609. var my=(!FOOPLOT_MSIE?e.pageY:(document.body.scrollTop+event.clientY))-offy;
  610. _self.dragInitX=mx;
  611. _self.dragInitY=my;
  612. _self.isMouseDown=true;
  613. switch(_self.selectedMode) {
  614. case FOOPLOT_MODE_ZOOMBOX:
  615. _self.zoomboxBox.style.left=mx+'px';
  616. _self.zoomboxBox.style.top=my+'px';
  617. _self.zoomboxBox.style.width='0px';
  618. _self.zoomboxBox.style.height='0px';
  619. _self.zoomboxBox.style.visibility='visible';
  620. break;
  621. }
  622. }
  623. this.container.onmouseup=function(e) {
  624. if(e === null) e = window.event;
  625. if(e && e.preventDefault) e.preventDefault();
  626. for (i in FOOPLOT_INSTANCES) if(this===FOOPLOT_INSTANCES[i].container) var _self=FOOPLOT_INSTANCES[i];
  627. var offx=_self.container.offsetLeft;
  628. var offy=_self.container.offsetTop;
  629. if(_self.container.parentNode) {
  630. offx+=_self.container.parentNode.offsetLeft;
  631. offy+=_self.container.parentNode.offsetTop;
  632. }
  633. var mx=(!FOOPLOT_MSIE?e.pageX:(document.body.scrollLeft+event.clientX))-offx;
  634. var my=(!FOOPLOT_MSIE?e.pageY:(document.body.scrollTop+event.clientY))-offy;
  635. if(_self.isMouseDown) {
  636. _self.isMouseDown=false;
  637. switch(_self.selectedMode) {
  638. case FOOPLOT_MODE_MOVE:
  639. if(_self.zoomTimeout) return null;
  640. var dx=_self.dpx/_self.width*(_self.xmax-_self.xmin);
  641. var dy=_self.dpy/_self.height*(_self.ymax-_self.ymin);
  642. _self.xmin-=dx;
  643. _self.xmax-=dx;
  644. _self.ymin+=dy;
  645. _self.ymax+=dy;
  646. _self.dpx=0;
  647. _self.dpy=0;
  648. _self.subcontainer.style.left='0px';
  649. _self.subcontainer.style.top='0px';
  650. _self.reDraw();
  651. _self.onWindowChange([_self.xmin,_self.xmax,_self.ymin,_self.ymax]);
  652. break;
  653. case FOOPLOT_MODE_ZOOMBOX:
  654. _self.zoomboxBox.style.visibility='hidden';
  655. _self.selectMode(FOOPLOT_MODE_MOVE);
  656. if(parseInt(_self.zoomboxBox.style.width)>5 && parseInt(_self.zoomboxBox.style.height)>5) {
  657. var newxmin=(parseInt(_self.zoomboxBox.style.left)/_self.width)*(_self.xmax-_self.xmin)+_self.xmin;
  658. var newymax=_self.ymax-(parseInt(_self.zoomboxBox.style.top)/_self.height)*(_self.ymax-_self.ymin);
  659. var newxmax=((parseInt(_self.zoomboxBox.style.left)+parseInt(_self.zoomboxBox.style.width))/_self.width)*(_self.xmax-_self.xmin)+_self.xmin;
  660. var newymin=_self.ymax-((parseInt(_self.zoomboxBox.style.top)+parseInt(_self.zoomboxBox.style.height))/_self.height)*(_self.ymax-_self.ymin);
  661. _self.xmin=newxmin;
  662. _self.xmax=newxmax;
  663. _self.ymin=newymin;
  664. _self.ymax=newymax;
  665. _self.reDraw();
  666. _self.onWindowChange([_self.xmin,_self.xmax,_self.ymin,_self.ymax]);
  667. }
  668. break;
  669. case FOOPLOT_MODE_INTERSECTION:
  670. _self.showIntersection(_self.dragInitX/_self.width*(_self.xmax-_self.xmin)+_self.xmin);
  671. break;
  672. case FOOPLOT_MODE_TRACE:
  673. var initx=mx/_self.width*(_self.xmax-_self.xmin)+_self.xmin;
  674. var order=Math.pow(10,2-Math.floor(Math.log(_self.xmax-_self.xmin)/Math.log(10)));
  675. var initx=parseFloat(Math.floor(initx*order)/order);
  676. var inity=(1-my/_self.height)*(_self.ymax-_self.ymin)+_self.ymin;
  677. _self.showTrace(initx,inity);
  678. break;
  679. }
  680. }
  681. }
  682. this.lastTouch=null;
  683. this.container.ontouchmove=function(e) {
  684. e.preventDefault();
  685. for (i in FOOPLOT_INSTANCES) if(this===FOOPLOT_INSTANCES[i].container) var _self=FOOPLOT_INSTANCES[i];
  686. if(e.touches.length>=1) {
  687. var _touch=e.touches[0];
  688. _self.lastTouch=_touch;
  689. this.mousemove(_touch);
  690. } else {
  691. _self.lastTouch=null;
  692. }
  693. }
  694. this.container.ontouchstart=function(e) {
  695. e.preventDefault();
  696. for (i in FOOPLOT_INSTANCES) if(this===FOOPLOT_INSTANCES[i].container) var _self=FOOPLOT_INSTANCES[i];
  697. if(e.touches.length>=1) {
  698. var _touch=e.touches[0];
  699. _self.lastTouch=_touch;
  700. this.mousedown(_touch);
  701. } else {
  702. _self.lastTouch=null;
  703. }
  704. }
  705. this.container.ontouchend=function(e) {
  706. e.preventDefault();
  707. for (i in FOOPLOT_INSTANCES) if(this===FOOPLOT_INSTANCES[i].container) var _self=FOOPLOT_INSTANCES[i];
  708. if(e.touches.length==0) {
  709. this.mouseup(_self.lastTouch);
  710. _self.lastTouch=null;
  711. }
  712. }
  713. this.onmousewheel=function(e) {
  714. if(e === null) e = window.event;
  715. for (i in FOOPLOT_INSTANCES) if(this===FOOPLOT_INSTANCES[i].container) var _self=FOOPLOT_INSTANCES[i];
  716. if(e && e.preventDefault) e.preventDefault();
  717. else e.returnValue=false;
  718. if(e && (e.srcElement?e.srcElement:e.target).className=='fooplot-tool') return null;
  719. var delta = 0;
  720. if(e.wheelDelta) { /* IE/Opera. */
  721. delta = e.wheelDelta/120;
  722. } else if (e.detail) { /** Mozilla case. */
  723. delta = -e.detail/3;
  724. }
  725. if(delta>0 && _self.zoomPendingFactor<=8) _self.zoom(1.25);
  726. else if(delta<0 && _self.zoomPendingFactor>=0.125) _self.zoom(0.8);
  727. else if(delta) _self.zoom(1);
  728. }
  729. this.container.onmousewheel=this.onmousewheel;
  730. if(this.container.addEventListener) {
  731. this.container.addEventListener('DOMMouseScroll',this.onmousewheel,false);
  732. }
  733. this.drawPoints=function(points) {
  734. if(points.length) {
  735. for(i in points) {
  736. if(points[i].length==2) {
  737. px=(points[i][0]-this.xmin)/(this.xmax-this.xmin)*this.width;
  738. py=(this.ymax-points[i][1])/(this.ymax-this.ymin)*this.height;
  739. if(!isNaN(px) && !isNaN(py) && 0<=px && px<=this.width && 0<=py && py<=this.height) {
  740. this.context.fillRect(px-2,py-2,5,5);
  741. }
  742. }
  743. }
  744. }
  745. }
  746. this.drawFunction=function(jeq) {
  747. var px,py,y,thisIsNaN=false,lastOnScreen,lastIsNaN=false,thisOnScreen,lastpx,lastpy,lasty,lastychange,isRedoLoop=false;
  748. this.context.beginPath();
  749. var started=false;
  750. var pxstep=FOOPLOT_MSIE?1:0.25;
  751. this.context.moveTo(-10,this.height/2);
  752. for(px=0;px<this.width;px+=pxstep) {
  753. this.vars.x=(px/this.width)*(this.xmax-this.xmin)+this.xmin;
  754. y=jeq(this.vars);
  755. if(isNaN(y)) {
  756. if(!lastIsNaN) {
  757. if(lastychange>0 && lasty>0) this.context.lineTo(lastpx,0);
  758. else if(lastychange<0 && lasty<0) this.context.lineTo(lastpx,this.height);
  759. }
  760. thisOnScreen=false;
  761. thisIsNaN=true;
  762. } else {
  763. py=(this.ymax-y)/(this.ymax-this.ymin)*this.height;
  764. thisOnScreen=py>=0 && py<=this.height;
  765. if(py>this.height+100) py=this.height+100;
  766. if(py<-100) py=-100;
  767. thisIsNaN=false;
  768. }
  769. if(pxstep>.001 && thisOnScreen && !lastOnScreen) {
  770. px-=pxstep;
  771. pxstep/=10;
  772. } else {
  773. if(!(lastIsNaN || thisIsNaN) && (lastOnScreen || thisOnScreen)) {
  774. this.context.lineTo(px,py);
  775. } else {
  776. if(!lastIsNaN && !thisIsNaN && lasty*y<0 && lasty*y>Math.min(-10,this.ymin-this.ymax)) {
  777. this.context.lineTo(px,py);
  778. } else {
  779. this.context.moveTo(px,py);
  780. }
  781. }
  782. if(thisOnScreen) pxstep=pxstep/Math.abs(lastpy-py);
  783. else pxstep=1;
  784. if(pxstep>2) pxstep=2;
  785. else if(pxstep<0.001) pxstep=0.001;
  786. else if(isNaN(pxstep)) pxstep=1;
  787. lastpx=px;
  788. lastpy=py;
  789. lasty=y;
  790. lastOnScreen=thisOnScreen;
  791. lastIsNaN=thisIsNaN;
  792. }
  793. }
  794. this.context.stroke();
  795. }
  796. this.drawPolar=function(jeq,thetamin,thetamax,thetastep) {
  797. var px,py,x,y,r,lastOnScreen,thisOnScreen;
  798. this.context.beginPath();
  799. var points='';
  800. var started=false;
  801. for(this.vars.theta=thetamin;this.vars.theta<=thetamax;this.vars.theta+=thetastep) {
  802. r=jeq(this.vars);
  803. x=r*Math.cos(this.vars.theta);
  804. y=r*Math.sin(this.vars.theta);
  805. if(isNaN(y)||isNaN(x))
  806. thisOnScreen=false;
  807. else {
  808. px=(x-this.xmin)/(this.xmax-this.xmin)*this.width;
  809. py=(this.ymax-y)/(this.ymax-this.ymin)*this.height;
  810. if(!started) { started=true;this.context.moveTo(px,py); }
  811. thisOnScreen=(px>0 && px<this.width && py>0 && py<this.height);
  812. if(lastOnScreen || thisOnScreen) {
  813. this.context.lineTo(px,py);
  814. } else {
  815. this.context.moveTo(px,py);
  816. }
  817. }
  818. lastOnScreen=thisOnScreen;
  819. }
  820. this.context.stroke();
  821. }
  822. this.drawParametric=function(jeqx,jeqy,smin,smax,sstep) {
  823. var px,py,x,y,lastOnScreen,thisOnScreen;
  824. var started=false;
  825. this.context.beginPath();
  826. for(this.vars.s=smin;this.vars.s<=smax;this.vars.s+=sstep) {
  827. x=jeqx(this.vars);
  828. y=jeqy(this.vars);
  829. if(isNaN(y)||isNaN(x))
  830. thisOnScreen=false;
  831. else {
  832. px=(x-this.xmin)/(this.xmax-this.xmin)*this.width;
  833. py=(this.ymax-y)/(this.ymax-this.ymin)*this.height;
  834. if(!started) { started=true; this.context.moveTo(px,py); }
  835. thisOnScreen=(px>0 && px<this.width && py>0 && py<this.height);
  836. if(lastOnScreen || thisOnScreen) {
  837. this.context.lineTo(px,py);
  838. } else {
  839. this.context.moveTo(px,py);
  840. }
  841. }
  842. lastOnScreen=thisOnScreen;
  843. }
  844. this.context.stroke();
  845. }
  846. this.findIntersection=function(jeq0,jeq1,initx) {
  847. var xpd,y0,y1,y,ypd,d=.0000000001;
  848. var i=0;
  849. if(jeq0===null) return 0;
  850. if(jeq1===null) { jeq1=function() { return 0; } }
  851. this.vars.x=initx;
  852. y0=jeq0(this.vars);
  853. y1=jeq1(this.vars);
  854. y=y0-y1;
  855. while(i<100) {
  856. i++;
  857. y0=jeq0(this.vars);
  858. y1=jeq1(this.vars);
  859. y=y0-y1;
  860. this.vars.x+=d;
  861. y0=jeq0(this.vars);
  862. y1=jeq1(this.vars);
  863. ypd=y0-y1;
  864. if(y-ypd!=0) {
  865. this.vars.x+=y*d/(y-ypd);
  866. }
  867. }
  868. y0=jeq0(this.vars);
  869. y1=jeq1(this.vars);
  870. y=y0-y1;
  871. if(isNaN(this.vars.x)) return null;
  872. if(Math.abs(y)>1e-9) return null;
  873. else return parseFloat(this.vars.x.toFixed(9));
  874. }
  875. this.showTrace=function(initx,inity) {
  876. var i,y,minDistance=1e10,besti=null,besty=null;
  877. this.vars.x=initx;
  878. for(i in this.plots) {
  879. if(this.plots[i]['type']===FOOPLOT_TYPE_FUNCTION) {
  880. y=this.plots[i].jeq(this.vars);
  881. if(Math.abs(y-inity)<minDistance) { minDistance=Math.abs(y-inity);besti=i;besty=y; }
  882. }
  883. }
  884. if(besti===null) return null;
  885. px=(initx-this.xmin)/(this.xmax-this.xmin)*this.width;
  886. py=(this.ymax-besty)/(this.ymax-this.ymin)*this.height;
  887. this.tracePoint.style.visibility='';
  888. this.tracePoint.style.left=(px-3)+'px';
  889. this.tracePoint.style.top=(py-3)+'px';
  890. this.traceDisplay.style.visibility='';
  891. this.traceDisplay.style.left=px+'px';
  892. this.traceDisplay.style.top=(py+8-(py>this.height/2?48:0))+'px';
  893. this.traceDisplayText.nodeValue=('('+parseFloat(initx.toFixed(9))+','+parseFloat(besty.toFixed(9))+')');
  894. }
  895. this.showIntersection=function(initx) {
  896. this.vars.x=initx;
  897. var i,j,yi,yj,minDistance=1e10,bestPair=null;
  898. this.plotstmp=this.plots.slice(0);
  899. this.plotstmp.unshift({'type':FOOPLOT_TYPE_FUNCTION,'jeq':function() {return 0;}});
  900. for(i in this.plotstmp) {
  901. if(this.plotstmp[i]['type']===FOOPLOT_TYPE_FUNCTION) {
  902. for(j in this.plotstmp) {
  903. if(i!=j && this.plotstmp[j]['type']===FOOPLOT_TYPE_FUNCTION) {
  904. yi=this.plotstmp[i].jeq(this.vars);
  905. yj=this.plotstmp[j].jeq(this.vars);
  906. if(Math.abs(yi-yj)<minDistance) { minDistance=Math.abs(yi-yj);bestPair=[i,j]; }
  907. }
  908. }
  909. }
  910. }
  911. if(bestPair===null) return null;
  912. xroot=this.findIntersection(this.plotstmp[bestPair[0]].jeq,this.plotstmp[bestPair[1]].jeq,initx);
  913. y=this.plotstmp[bestPair[0]].jeq(this.vars);
  914. y=parseFloat(y.toFixed(9));
  915. px=(xroot-this.xmin)/(this.xmax-this.xmin)*this.width;
  916. py=(this.ymax-y)/(this.ymax-this.ymin)*this.height;
  917. if(xroot!=null) {
  918. this.intersectionPoint.style.visibility='';
  919. this.intersectionPoint.style.left=(px-3)+'px';
  920. this.intersectionPoint.style.top=(py-3)+'px';
  921. this.intersectionDisplay.style.visibility='';
  922. this.intersectionDisplay.style.left=px+'px';
  923. this.intersectionDisplay.style.top=(py+8-(py>this.height/2?48:0))+'px';
  924. this.intersectionDisplayText.nodeValue=('('+xroot+','+y+')');
  925. }
  926. }
  927. this.tryToMakeFraction=function(testnum) {
  928. var numerator,denominator;
  929. // check if it is a nice fraction
  930. for(var denominator=1;denominator<16;denominator++) {
  931. numerator=(testnum*denominator).toFixed(9);
  932. if(numerator.indexOf('.000000000')!=-1) return parseFloat(numerator)+(denominator===1?'':'/'+denominator);
  933. }
  934. // return the decimal back if we fail to get a nice fraction
  935. return parseFloat(testnum.toFixed(9)).toString();
  936. }
  937. // PRIVATE: EQUATION PARSNIP
  938. this.parseEquationError='';
  939. this.parseEquationHasElement=function(v,e) {for(var i=0;i<v.length;i++) if(v[i]===e) return true;return false;}
  940. this.parseEquationFixPowers=function(v) {
  941. if(v===null) { this.parseEquationError?null:this.parseEquationError="syntax error"; return null; }
  942. for(i=0;i<v.length;i++) {
  943. if(this.parseEquationIsArray(v[i])) { v[i]=this.parseEquationFixPowers(v[i]); if(v[i]===null) { this.parseEquationError?null:this.parseEquationError="syntax error"; return null; } }
  944. }
  945. for(var i=0;i<v.length;i++) {
  946. if(v[i]==='^') {
  947. if(v[i-1]===null||v[i+1]===null) { this.parseEquationError="^ requires two arguments, for example x^2 or (x+1)^(x+2)."; return null; }
  948. v.splice(i-1,3,new Array('Math.pow',new Array('(',v[i-1],',',v[i+1],')')));
  949. i-=2;
  950. }
  951. }
  952. return v;
  953. }
  954. this.parseEquationFixFunctions=function(v) {
  955. if(v===null) {this.parseEquationError?null:this.parseEquationError="syntax error"; return null;}
  956. for(i=0;i<v.length;i++) {
  957. if(this.parseEquationIsArray(v[i])) {
  958. v[i]=this.parseEquationFixFunctions(v[i]);
  959. if(v[i]===null) {this.parseEquationError?null:this.parseEquationError="syntax error"; return null;}
  960. }
  961. }
  962. for(var i=0;i<v.length;i++) {
  963. if(!this.parseEquationIsArray(v[i])) {
  964. if(FOOPLOT_MATH[v[i]]!=undefined) {
  965. if(v[i+1]===null) {this.parseEquationEror="function "+v[i]+ " requires an argument."; return null;}
  966. v[i]='FOOPLOT_MATH.'+v[i].toLowerCase();
  967. v.splice(i,2,new Array('(',v[i],v[i+1],')'));
  968. i--;
  969. }
  970. }
  971. }
  972. return v;
  973. }
  974. this.parseEquationIsArray=function(v) {if(v==null){return 0;}if(v.constructor.toString().indexOf("Array")===-1)return false;return true;}
  975. this.parseEquationJoinArray=function(v) {var t="";for(var i=0;i<v.length;i++){if(this.parseEquationIsArray(v[i])){ t+=this.parseEquationJoinArray(v[i]);}else{t+=v[i];}}return t;}
  976. this.parseEquation=function(eq,vars) {
  977. var jeq=null;
  978. var tokens;
  979. var e,i;
  980. var pstart=-1,pend;
  981. if(!vars) vars=this.vars;
  982. jeq_error="";
  983. e=eq.replace(/ /g,"");
  984. e=e.replace(/([0-9])([a-df-z]|[a-z][a-z]|\()/ig,"$1*$2");
  985. e=e.replace(/(\))([0-9a-df-z]|[a-z][a-z]|\()/ig,"$1*$2");
  986. e=e.replace(/([a-z0-9\.])([^a-z0-9\.])/ig,"$1 $2");
  987. e=e.replace(/([^a-z0-9\.])([a-z0-9\.])/ig,"$1 $2");
  988. e=e.replace(/(\-|\)|\()/g," $1 ");
  989. tokens=e.split(/ +/);
  990. for(i=0;i<tokens.length;i++) {
  991. tokens[i]=tokens[i].replace(/ /g,"");
  992. tokens[i]=tokens[i].replace(/_/g,".");
  993. if(tokens[i]==='') { tokens.splice(i,1);i--; }
  994. else if(tokens[i].match(/^[a-z][a-z0-9]*$/i) && vars[tokens[i]]!=undefined) { tokens[i]='vars.'+tokens[i]; }
  995. else if(tokens[i].length>0 && tokens[i].match(/^[a-z][a-z0-9]*$/i) && FOOPLOT_MATH[tokens[i]]===undefined) { this.parseEquationError="invalid variable or function: "+tokens[i];return null; }
  996. }
  997. while(this.parseEquationHasElement(tokens,'(')||this.parseEquationHasElement(tokens,')')) {
  998. pstart=-1;
  999. for(i=0;i<tokens.length;i++) {
  1000. if(tokens[i]==='(') pstart=i;
  1001. if(tokens[i]===')'&&pstart==-1) { this.parseEquationError="unmatched right parenthesis )";return null; }
  1002. if(tokens[i]===')'&&pstart!=-1) {
  1003. tokens.splice(pstart,i-pstart+1,tokens.slice(pstart,i+1));
  1004. i=-1;
  1005. pstart=-1;
  1006. }
  1007. }
  1008. if(pstart!=-1) { this.parseEquationError="unmatched left parenthesis (";return null; }
  1009. }
  1010. tokens=this.parseEquationFixFunctions(tokens);
  1011. if(tokens===null) { return null; }
  1012. tokens=this.parseEquationFixPowers(tokens);
  1013. if(tokens===null) { return null; }
  1014. eval('jeq=function(vars) { return '+this.parseEquationJoinArray(tokens)+'; }');
  1015. return jeq;
  1016. }
  1017. this.parseConst=function(eq) {
  1018. var consts={'pi':this.vars.pi,'e':this.vars.e};
  1019. var f=this.parseEquation(eq,consts);
  1020. if(f)
  1021. return parseFloat(f(consts));
  1022. else {
  1023. alert(this.parseEquationError);
  1024. return 0;
  1025. }
  1026. }
  1027. // PUBLIC
  1028. this.reDraw=function() {
  1029. this.hideIntersection();
  1030. this.hideTrace();
  1031. this.clear();
  1032. if(this.showGrid) {
  1033. this.context.strokeStyle=this.gridColor;
  1034. this.context.fillStyle=this.gridColor;
  1035. this.drawGrid();
  1036. }
  1037. if(this.showAxes) {
  1038. this.context.strokeStyle=this.axesColor;
  1039. this.context.fillStyle=this.axesColor;
  1040. this.drawAxes();
  1041. }
  1042. if(this.showLabels) {
  1043. this.context.fillStyle=this.labelsColor;
  1044. this.drawLabels();
  1045. }
  1046. for(var i in this.plots) {
  1047. this.context.strokeStyle='#000000';
  1048. switch(this.plots[i].type) {
  1049. case FOOPLOT_TYPE_FUNCTION:
  1050. this.context.strokeStyle=this.plots[i].options.color;
  1051. this.drawFunction(this.plots[i].jeq);
  1052. break;
  1053. case FOOPLOT_TYPE_POLAR:
  1054. this.context.strokeStyle=this.plots[i].options.color;
  1055. this.drawPolar(this.plots[i].jeq,this.plots[i].options.thetamin,this.plots[i].options.thetamax,this.plots[i].options.thetastep);
  1056. break;
  1057. case FOOPLOT_TYPE_PARAMETRIC:
  1058. this.context.strokeStyle=this.plots[i].options.color;
  1059. this.drawParametric(this.plots[i].jeqx,this.plots[i].jeqy,this.plots[i].options.smin,this.plots[i].options.smax,this.plots[i].options.sstep);
  1060. break;
  1061. case FOOPLOT_TYPE_POINTS:
  1062. this.context.fillStyle=this.plots[i].options.color;
  1063. this.drawPoints(this.plots[i].eq);
  1064. break;
  1065. }
  1066. }
  1067. if(FOOPLOT_MSIE) {
  1068. // hack for MSIE + excanvas which sometimes sporadically does not display the last-drawn path
  1069. // so we draw a dummy path
  1070. this.context.beginPath();
  1071. this.context.moveTo(0,-1);
  1072. this.context.lineTo(-1,-1);
  1073. this.context.stroke();
  1074. }
  1075. }
  1076. this.addPlot=function(eq,type,options) {
  1077. if(!type) type=FOOPLOT_TYPE_FUNCTION;
  1078. if(!options) options={};
  1079. if(!options.color) options.color='#000000';
  1080. if(!options.width) options.width=1;
  1081. switch(type) {
  1082. case FOOPLOT_TYPE_FUNCTION:
  1083. var jeq=this.parseEquation(eq);
  1084. if(!jeq) {alert(this.parseEquationError);return null;}
  1085. this.plots.push({'id':this.plotlastid++,'type':type,'eq':eq,'jeq':jeq,'options':options});
  1086. break;
  1087. case FOOPLOT_TYPE_POLAR:
  1088. var jeq=this.parseEquation(eq);
  1089. if(!jeq) {alert(this.parseEquationError);return null;}
  1090. options['thetamin']=options['thetamin']?this.parseConst(options['thetamin']):0;
  1091. options['thetamax']=options['thetamax']?this.parseConst(options['thetamax']):2*this.vars.pi;
  1092. options['thetastep']=options['thetastep']?this.parseConst(options['thetastep']):0.01;
  1093. if(options['thetastep']<=0) options['thetastep']=0.01;
  1094. this.plots.push({'id':this.plotlastid++,'type':type,'eq':eq,'jeq':jeq,'options':options});
  1095. break;
  1096. case FOOPLOT_TYPE_PARAMETRIC:
  1097. var jeqx=this.parseEquation(eq[0]);
  1098. if(!jeqx) {alert(this.parseEquationError);return null;}
  1099. var jeqy=this.parseEquation(eq[1]);
  1100. if(!jeqy) {alert(this.parseEquationError);return null;}
  1101. options['smin']=options['smin']?this.parseConst(options['smin']):0;
  1102. options['smax']=options['smax']?this.parseConst(options['smax']):10;
  1103. options['sstep']=options['sstep']?this.parseConst(options['sstep']):0.01;
  1104. this.plots.push({'id':this.plotlastid++,'type':type,'eq':eq,'jeqx':jeqx,'jeqy':jeqy,'options':options});
  1105. break;
  1106. case FOOPLOT_TYPE_POINTS:
  1107. if(eq.length===null) return null;
  1108. this.plots.push({'id':this.plotlastid++,'type':type,'eq':eq,'options':options});
  1109. break;
  1110. default:
  1111. alert("Error: invalid plot type");
  1112. }
  1113. return this.plots.size;
  1114. }
  1115. this.deletePlot=function(plotid) {
  1116. // not yet implemented
  1117. }
  1118. this.deleteAllPlots=function() {
  1119. this.plots=[];
  1120. }
  1121. this.getGrid=function() {
  1122. return [this.xgrid,this.ygrid];
  1123. }
  1124. this.setGrid=function(g) {
  1125. g[0]=g[0].replace(' ','');
  1126. if(g[0]) {
  1127. g[0]=this.parseConst(g[0]); if(g[0]<=0) g[0]=1;
  1128. this.xgrid=g[0];
  1129. if((720*g[0]/this.vars.pi).toFixed(6).match(/0000$/)!==null) {
  1130. this.xgridunits=FOOPLOT_UNITS_PI;
  1131. } else if((720*g[0]/this.vars.e).toFixed(6).match(/0000$/)!==null) {
  1132. this.xgridunits=FOOPLOT_UNITS_E;
  1133. } else {
  1134. this.xgridunits=null;
  1135. }
  1136. } else {
  1137. this.xgrid=null;
  1138. this.xgridunits=null;
  1139. }
  1140. g[1]=g[1].replace(' ','');
  1141. if(g[1]) {
  1142. g[1]=this.parseConst(g[1]); if(g[1]<=0) g[1]=1;
  1143. this.ygrid=g[1];
  1144. if((720*g[1]/this.vars.pi).toFixed(6).match(/0000$/)!==null) {
  1145. this.ygridunits=FOOPLOT_UNITS_PI;
  1146. } else if((720*g[1]/this.vars.e).toFixed(6).match(/0000$/)!==null) {
  1147. this.ygridunits=FOOPLOT_UNITS_E;
  1148. } else {
  1149. this.ygridunits=null;
  1150. }
  1151. } else {
  1152. this.ygrid=null;
  1153. this.ygridunits=null;
  1154. }
  1155. }
  1156. this.getWindow=function() {
  1157. return [this.xmin,this.xmax,this.ymin,this.ymax];
  1158. }
  1159. this.setWindow=function(w) {
  1160. w[0]=this.parseConst(w[0]);
  1161. w[1]=this.parseConst(w[1]);
  1162. w[2]=this.parseConst(w[2]);
  1163. w[3]=this.parseConst(w[3]);
  1164. if(w[1]>w[0] && w[3]>w[2]) {
  1165. this.xmin=w[0];
  1166. this.xmax=w[1];
  1167. this.ymin=w[2];
  1168. this.ymax=w[3];
  1169. } else {
  1170. // throw error
  1171. }
  1172. }
  1173. this.setBackgroundColor=function(color) {
  1174. this.backgroundColor=color;
  1175. this.container.style.background=color;
  1176. this.subcontainer.style.background=color;
  1177. }
  1178. this.setLabelsColor=function(color) {
  1179. this.labelsColor=color;
  1180. }
  1181. this.setAxesColor=function(color) {
  1182. this.axesColor=color;
  1183. }
  1184. this.setGridColor=function(color) {
  1185. this.gridColor=color;
  1186. }
  1187. this.setShowAxes=function(v) {
  1188. this.showAxes=v;
  1189. }
  1190. this.setShowTicks=function(v) {
  1191. this.showTicks=v;
  1192. }
  1193. this.setShowLabels=function(v) {
  1194. this.showLabels=v;
  1195. }
  1196. this.setShowGrid=function(v) {
  1197. this.showGrid=v;
  1198. }
  1199. this.getSVG=function() {
  1200. this.context=this.recorder;
  1201. this.reDraw();
  1202. this.context=this.canvas.getContext('2d');
  1203. return this.recorder.getSVG();
  1204. }
  1205. // EVENTS (user-settable functions)
  1206. this.onWindowChange=function(w) {};
  1207. }
  1208. // interface "constants"
  1209. var FOOPLOT_TYPE_FUNCTION=0;
  1210. var FOOPLOT_TYPE_POLAR=1;
  1211. var FOOPLOT_TYPE_PARAMETRIC=2;
  1212. var FOOPLOT_TYPE_POINTS=3;
  1213. var FOOPLOT_MODE_MOVE=1;
  1214. var FOOPLOT_MODE_ZOOMBOX=2;
  1215. var FOOPLOT_MODE_INTERSECTION=3;
  1216. var FOOPLOT_MODE_TRACE=4;
  1217. var FOOPLOT_UNITS_PI=1;
  1218. var FOOPLOT_UNITS_E=2;
  1219. // keeps track of all instances, mainly for being able to access the class in event handlers
  1220. var FOOPLOT_INSTANCES=[];
  1221. // extended math functions
  1222. FOOPLOT_MATH={};
  1223. FOOPLOT_MATH.sin=Math.sin;
  1224. FOOPLOT_MATH.cos=Math.cos;
  1225. FOOPLOT_MATH.tan=Math.tan;
  1226. FOOPLOT_MATH.asin=Math.asin;
  1227. FOOPLOT_MATH.acos=Math.acos;
  1228. FOOPLOT_MATH.atan=Math.atan;
  1229. FOOPLOT_MATH.abs=Math.abs;
  1230. FOOPLOT_MATH.floor=Math.floor;
  1231. FOOPLOT_MATH.ceil=Math.ceil;
  1232. FOOPLOT_MATH.exp=Math.exp;
  1233. FOOPLOT_MATH.sqrt=Math.sqrt;
  1234. FOOPLOT_MATH.max=Math.max;
  1235. FOOPLOT_MATH.min=Math.min;
  1236. FOOPLOT_MATH.sec=function(x) { return 1/Math.cos(x); }
  1237. FOOPLOT_MATH.csc=function(x) { return 1/Math.sin(x); }
  1238. FOOPLOT_MATH.cot=function(x) { return 1/Math.tan(x); }
  1239. FOOPLOT_MATH.asec=function(x) { return Math.acos(1/x); }
  1240. FOOPLOT_MATH.acsc=function(x) { return Math.asin(1/x); }
  1241. FOOPLOT_MATH.acot=function(x) { return Math.atan(1/x); }
  1242. FOOPLOT_MATH.ln=function(x) { return Math.log(x); }
  1243. FOOPLOT_MATH.log=function(x) { return Math.log(x)/Math.log(10); }
  1244. FOOPLOT_MATH.sinh=function(x) { return (Math.exp(x)-Math.exp(-x))/2; }
  1245. FOOPLOT_MATH.cosh=function(x) { return (Math.exp(x)+Math.exp(-x))/2; }
  1246. FOOPLOT_MATH.tanh=function(x) { return (Math.exp(x)-Math.exp(-x))/(Math.exp(x)+Math.exp(-x)); }
  1247. FOOPLOT_MATH.asinh=function(x) { return Math.log(x+Math.sqrt(x*x+1)); }
  1248. FOOPLOT_MATH.acosh=function(x) { return Math.log(x+Math.sqrt(x*x-1)); }
  1249. FOOPLOT_MATH.atanh=function(x) { return 0.5*Math.log((1+x)/(1-x)); }
  1250. FOOPLOT_MATH.sech=function(x) { return 2/(Math.exp(x)+Math.exp(-x)); }
  1251. FOOPLOT_MATH.csch=function(x) { return 2/(Math.exp(x)-Math.exp(-x)); }
  1252. FOOPLOT_MATH.coth=function(x) { return (Math.exp(x)+Math.exp(-x))/(Math.exp(x)-Math.exp(-x)); }
  1253. FOOPLOT_MATH.asech=function(x) { return Math.log(1/x+Math.sqrt(1/x/x-1)); }
  1254. FOOPLOT_MATH.acsch=function(x) { return Math.log(1/x+Math.sqrt(1/x/x+1)); }
  1255. FOOPLOT_MATH.acoth=function(x) { return 0.5*Math.log((1+x)/(1-x)); }
  1256. FOOPLOT_MATH.u=function(x) { return (x>=0); }
  1257. var FOOPLOT_MSIE=(navigator.userAgent.toLowerCase().indexOf('msie')!=-1);
  1258. var FOOPLOT_TRANSITIONS=function() {
  1259. var b = document.body||document.documentElement;
  1260. var s = b.style;
  1261. var p = 'transition';
  1262. if(typeof s[p] === 'string') {return true; }
  1263. v = ['Moz', 'Webkit', 'Khtml', 'O', 'ms'],
  1264. p = p.charAt(0).toUpperCase() + p.substr(1);
  1265. for(var i=0; i<v.length; i++) {
  1266. if(typeof s[v[i] + p] === 'string') { return true; }
  1267. }
  1268. return false;
  1269. }();