RGraph.line.js 142 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717
  1. // version: 2014-06-26
  2. /**
  3. * o--------------------------------------------------------------------------------o
  4. * | This file is part of the RGraph package. RGraph is Free Software, licensed |
  5. * | under the MIT license - so it's free to use for all purposes. If you want to |
  6. * | donate to help keep the project going then you can do so here: |
  7. * | |
  8. * | http://www.rgraph.net/donate |
  9. * o--------------------------------------------------------------------------------o
  10. */
  11. RGraph = window.RGraph || {isRGraph: true};
  12. /**
  13. * The line chart constructor
  14. *
  15. * @param object canvas The cxanvas object
  16. * @param array ... The lines to plot
  17. */
  18. RGraph.Line = function (id)
  19. {
  20. var tmp = RGraph.getCanvasTag(id);
  21. // Get the canvas and context objects
  22. this.id = tmp[0];
  23. this.canvas = tmp[1];
  24. this.context = this.canvas.getContext ? this.canvas.getContext("2d", {alpha: (typeof id === 'object' && id.alpha === false) ? false : true}) : null;
  25. this.canvas.__object__ = this;
  26. this.type = 'line';
  27. this.max = 0;
  28. this.coords = [];
  29. this.coords2 = [];
  30. this.coords.key = [];
  31. this.coordsText = [];
  32. this.coordsSpline = [];
  33. this.hasnegativevalues = false;
  34. this.isRGraph = true;
  35. this.uid = RGraph.CreateUID();
  36. this.canvas.uid = this.canvas.uid ? this.canvas.uid : RGraph.CreateUID();
  37. this.colorsParsed = false;
  38. this.original_colors = [];
  39. this.firstDraw = true; // After the first draw this will be false
  40. /**
  41. * Compatibility with older browsers
  42. */
  43. //RGraph.OldBrowserCompat(this.context);
  44. // Various config type stuff
  45. this.properties =
  46. {
  47. 'chart.background.barcolor1': 'rgba(0,0,0,0)',
  48. 'chart.background.barcolor2': 'rgba(0,0,0,0)',
  49. 'chart.background.grid': 1,
  50. 'chart.background.grid.width': 1,
  51. 'chart.background.grid.hsize': 25,
  52. 'chart.background.grid.vsize': 25,
  53. 'chart.background.grid.color': '#ddd',
  54. 'chart.background.grid.vlines': true,
  55. 'chart.background.grid.hlines': true,
  56. 'chart.background.grid.border': true,
  57. 'chart.background.grid.autofit': true,
  58. 'chart.background.grid.autofit.align': false,
  59. 'chart.background.grid.autofit.numhlines': 5,
  60. 'chart.background.grid.autofit.numvlines': 20,
  61. 'chart.background.grid.dashed': false,
  62. 'chart.background.grid.dotted': false,
  63. 'chart.background.hbars': null,
  64. 'chart.background.image': null,
  65. 'chart.background.image.stretch': true,
  66. 'chart.background.image.x': null,
  67. 'chart.background.image.y': null,
  68. 'chart.background.image.w': null,
  69. 'chart.background.image.h': null,
  70. 'chart.background.image.align': null,
  71. 'chart.background.color': null,
  72. 'chart.labels': null,
  73. 'chart.labels.ingraph': null,
  74. 'chart.labels.above': false,
  75. 'chart.labels.above.size': 8,
  76. 'chart.labels.above.decimals': null,
  77. 'chart.xtickgap': 20,
  78. 'chart.smallxticks': 3,
  79. 'chart.largexticks': 5,
  80. 'chart.ytickgap': 20,
  81. 'chart.smallyticks': 3,
  82. 'chart.largeyticks': 5,
  83. 'chart.numyticks': 10,
  84. 'chart.linewidth': 2.01,
  85. 'chart.colors': ['red', '#0f0', '#00f', '#f0f', '#ff0', '#0ff','green','pink','blue','black'],
  86. 'chart.hmargin': 0,
  87. 'chart.tickmarks.dot.color': 'white',
  88. 'chart.tickmarks': 'endcircle',
  89. 'chart.tickmarks.linewidth': null,
  90. 'chart.ticksize': 3,
  91. 'chart.gutter.left': 25,
  92. 'chart.gutter.right': 25,
  93. 'chart.gutter.top': 25,
  94. 'chart.gutter.bottom': 25,
  95. 'chart.tickdirection': -1,
  96. 'chart.yaxispoints': 5,
  97. 'chart.fillstyle': null,
  98. 'chart.xaxispos': 'bottom',
  99. 'chart.yaxispos': 'left',
  100. 'chart.xticks': null,
  101. 'chart.text.size': 10,
  102. 'chart.text.angle': 0,
  103. 'chart.text.color': 'black',
  104. 'chart.text.font': 'Arial',
  105. 'chart.ymin': 0,
  106. 'chart.ymax': null,
  107. 'chart.title': '',
  108. 'chart.title.background': null,
  109. 'chart.title.hpos': null,
  110. 'chart.title.vpos': null,
  111. 'chart.title.bold': true,
  112. 'chart.title.font': null,
  113. 'chart.title.xaxis': '',
  114. 'chart.title.xaxis.bold': true,
  115. 'chart.title.xaxis.size': null,
  116. 'chart.title.xaxis.font': null,
  117. 'chart.title.yaxis': '',
  118. 'chart.title.yaxis.bold': true,
  119. 'chart.title.yaxis.size': null,
  120. 'chart.title.yaxis.font': null,
  121. 'chart.title.yaxis.color': null,
  122. 'chart.title.xaxis.pos': null,
  123. 'chart.title.yaxis.pos': null,
  124. 'chart.title.yaxis.x': null,
  125. 'chart.title.yaxis.y': null,
  126. 'chart.title.xaxis.x': null,
  127. 'chart.title.xaxis.y': null,
  128. 'chart.title.x': null,
  129. 'chart.title.y': null,
  130. 'chart.title.halign': null,
  131. 'chart.title.valign': null,
  132. 'chart.shadow': true,
  133. 'chart.shadow.offsetx': 2,
  134. 'chart.shadow.offsety': 2,
  135. 'chart.shadow.blur': 3,
  136. 'chart.shadow.color': 'rgba(0,0,0,0.5)',
  137. 'chart.tooltips': null,
  138. 'chart.tooltips.hotspot.xonly': false,
  139. 'chart.tooltips.hotspot.size': 5,
  140. 'chart.tooltips.effect': 'fade',
  141. 'chart.tooltips.css.class': 'RGraph_tooltip',
  142. 'chart.tooltips.event': 'onmousemove',
  143. 'chart.tooltips.highlight': true,
  144. 'chart.tooltips.coords.page': false,
  145. 'chart.highlight.stroke': 'gray',
  146. 'chart.highlight.fill': 'white',
  147. 'chart.stepped': false,
  148. 'chart.key': null,
  149. 'chart.key.background': 'white',
  150. 'chart.key.position': 'graph',
  151. 'chart.key.halign': null,
  152. 'chart.key.shadow': false,
  153. 'chart.key.shadow.color': '#666',
  154. 'chart.key.shadow.blur': 3,
  155. 'chart.key.shadow.offsetx': 2,
  156. 'chart.key.shadow.offsety': 2,
  157. 'chart.key.position.gutter.boxed': false,
  158. 'chart.key.position.x': null,
  159. 'chart.key.position.y': null,
  160. 'chart.key.color.shape': 'square',
  161. 'chart.key.rounded': true,
  162. 'chart.key.linewidth': 1,
  163. 'chart.key.colors': null,
  164. 'chart.key.interactive': false,
  165. 'chart.key.interactive.highlight.chart.stroke': 'rgba(255,0,0,0.3)',
  166. 'chart.key.interactive.highlight.label': 'rgba(255,0,0,0.2)',
  167. 'chart.key.text.color': 'black',
  168. 'chart.contextmenu': null,
  169. 'chart.ylabels': true,
  170. 'chart.ylabels.count': 5,
  171. 'chart.ylabels.inside': false,
  172. 'chart.scale.invert': false,
  173. 'chart.xlabels.inside': false,
  174. 'chart.xlabels.inside.color': 'rgba(255,255,255,0.5)',
  175. 'chart.noaxes': false,
  176. 'chart.noyaxis': false,
  177. 'chart.noxaxis': false,
  178. 'chart.noendxtick': false,
  179. 'chart.noendytick': false,
  180. 'chart.units.post': '',
  181. 'chart.units.pre': '',
  182. 'chart.scale.zerostart': false,
  183. 'chart.scale.decimals': null,
  184. 'chart.scale.point': '.',
  185. 'chart.scale.thousand': ',',
  186. 'chart.crosshairs': false,
  187. 'chart.crosshairs.color': '#333',
  188. 'chart.crosshairs.hline': true,
  189. 'chart.crosshairs.vline': true,
  190. 'chart.annotatable': false,
  191. 'chart.annotate.color': 'black',
  192. 'chart.axesontop': false,
  193. 'chart.filled': false,
  194. 'chart.filled.range': false,
  195. 'chart.filled.range.threshold': null,
  196. 'chart.filled.range.threshold.colors': ['red', 'green'],
  197. 'chart.filled.accumulative': true,
  198. 'chart.variant': null,
  199. 'chart.axis.color': 'black',
  200. 'chart.axis.linewidth': 1,
  201. 'chart.numxticks': (arguments[1] && typeof(arguments[1][0]) == 'number' ? arguments[1].length : 20),
  202. 'chart.numyticks': 10,
  203. 'chart.zoom.factor': 1.5,
  204. 'chart.zoom.fade.in': true,
  205. 'chart.zoom.fade.out': true,
  206. 'chart.zoom.hdir': 'right',
  207. 'chart.zoom.vdir': 'down',
  208. 'chart.zoom.frames': 25,
  209. 'chart.zoom.delay': 16.666,
  210. 'chart.zoom.shadow': true,
  211. 'chart.zoom.background': true,
  212. 'chart.zoom.action': 'zoom',
  213. 'chart.backdrop': false,
  214. 'chart.backdrop.size': 30,
  215. 'chart.backdrop.alpha': 0.2,
  216. 'chart.resizable': false,
  217. 'chart.resize.handle.adjust': [0,0],
  218. 'chart.resize.handle.background': null,
  219. 'chart.adjustable': false,
  220. 'chart.noredraw': false,
  221. 'chart.outofbounds': false,
  222. 'chart.chromefix': true,
  223. 'chart.animation.factor': 1,
  224. 'chart.animation.unfold.x': false,
  225. 'chart.animation.unfold.y': true,
  226. 'chart.animation.unfold.initial': 2,
  227. 'chart.animation.trace.clip': 1,
  228. 'chart.curvy': false,
  229. 'chart.line.visible': true,
  230. 'chart.events.click': null,
  231. 'chart.events.mousemove': null
  232. }
  233. /**
  234. * Change null arguments to empty arrays
  235. */
  236. for (var i=1; i<arguments.length; ++i) {
  237. if (typeof(arguments[i]) == 'null' || !arguments[i]) {
  238. arguments[i] = [];
  239. }
  240. }
  241. /**
  242. * Store the original data. Thiss also allows for giving arguments as one big array.
  243. */
  244. this.original_data = [];
  245. for (var i=1; i<arguments.length; ++i) {
  246. if (arguments[1] && typeof(arguments[1]) == 'object' && arguments[1][0] && typeof(arguments[1][0]) == 'object' && arguments[1][0].length) {
  247. var tmp = [];
  248. for (var i=0; i<arguments[1].length; ++i) {
  249. tmp[i] = RGraph.array_clone(arguments[1][i]);
  250. }
  251. for (var j=0; j<tmp.length; ++j) {
  252. this.original_data[j] = RGraph.array_clone(tmp[j]);
  253. }
  254. } else {
  255. this.original_data[i - 1] = RGraph.array_clone(arguments[i]);
  256. }
  257. }
  258. // Check for support
  259. if (!this.canvas) {
  260. alert('[LINE] Fatal error: no canvas support');
  261. return;
  262. }
  263. /**
  264. * Store the data here as one big array
  265. */
  266. this.data_arr = RGraph.array_linearize(this.original_data);
  267. for (var i=0; i<this.data_arr.length; ++i) {
  268. this['$' + i] = {};
  269. }
  270. /**
  271. * Translate half a pixel for antialiasing purposes - but only if it hasn't beeen
  272. * done already
  273. */
  274. if (!this.canvas.__rgraph_aa_translated__) {
  275. this.context.translate(0.5,0.5);
  276. this.canvas.__rgraph_aa_translated__ = true;
  277. }
  278. // Short variable names
  279. var RG = RGraph;
  280. var ca = this.canvas;
  281. var co = ca.getContext('2d');
  282. var prop = this.properties;
  283. var jq = jQuery;
  284. var pa = RG.Path;
  285. var win = window;
  286. var doc = document;
  287. var ma = Math;
  288. /**
  289. * "Decorate" the object with the generic effects if the effects library has been included
  290. */
  291. if (RG.Effects && typeof RG.Effects.decorate === 'function') {
  292. RG.Effects.decorate(this);
  293. }
  294. /**
  295. * An all encompassing accessor
  296. *
  297. * @param string name The name of the property
  298. * @param mixed value The value of the property
  299. */
  300. this.set =
  301. this.Set = function (name, value)
  302. {
  303. name = name.toLowerCase();
  304. /**
  305. * This should be done first - prepend the propertyy name with "chart." if necessary
  306. */
  307. if (name.substr(0,6) != 'chart.') {
  308. name = 'chart.' + name;
  309. }
  310. // Consolidate the tooltips
  311. if (name == 'chart.tooltips' && typeof value == 'object' && value) {
  312. var tooltips = [];
  313. for (var i=1; i<arguments.length; i++) {
  314. if (typeof(arguments[i]) == 'object' && arguments[i][0]) {
  315. for (var j=0; j<arguments[i].length; j++) {
  316. tooltips.push(arguments[i][j]);
  317. }
  318. } else if (typeof(arguments[i]) == 'function') {
  319. tooltips = arguments[i];
  320. } else {
  321. tooltips.push(arguments[i]);
  322. }
  323. }
  324. // Because "value" is used further down at the end of this function, set it to the expanded array os tooltips
  325. value = tooltips;
  326. }
  327. /**
  328. * If (buggy) Chrome and the linewidth is 1, change it to 1.01
  329. */
  330. if (name == 'chart.linewidth' && navigator.userAgent.match(/Chrome/)) {
  331. if (value == 1) {
  332. value = 1.01;
  333. } else if (RGraph.is_array(value)) {
  334. for (var i=0; i<value.length; ++i) {
  335. if (typeof(value[i]) == 'number' && value[i] == 1) {
  336. value[i] = 1.01;
  337. }
  338. }
  339. }
  340. }
  341. /**
  342. * Check for xaxispos
  343. */
  344. if (name == 'chart.xaxispos' ) {
  345. if (value != 'bottom' && value != 'center' && value != 'top') {
  346. alert('[LINE] (' + this.id + ') chart.xaxispos should be top, center or bottom. Tried to set it to: ' + value + ' Changing it to center');
  347. value = 'center';
  348. }
  349. }
  350. /**
  351. * chart.xticks is now called chart.numxticks
  352. */
  353. if (name == 'chart.xticks') {
  354. name = 'chart.numxticks';
  355. }
  356. /**
  357. * Change the new chart.spline option to chart.curvy
  358. */
  359. if (name == 'chart.spline') {
  360. name = 'chart.curvy';
  361. }
  362. /**
  363. * Chnge chart.ylabels.invert to chart.scale.invert
  364. */
  365. if (name == 'chart.ylabels.invert') {
  366. name = 'chart.scale.invert';
  367. }
  368. this.properties[name] = value;
  369. return this;
  370. };
  371. /**
  372. * An all encompassing accessor
  373. *
  374. * @param string name The name of the property
  375. */
  376. this.get =
  377. this.Get = function (name)
  378. {
  379. /**
  380. * This should be done first - prepend the property name with "chart." if necessary
  381. */
  382. if (name.substr(0,6) != 'chart.') {
  383. name = 'chart.' + name;
  384. }
  385. /**
  386. * If requested property is chart.spline - change it to chart.curvy
  387. */
  388. if (name == 'chart.spline') {
  389. name = 'chart.curvy';
  390. }
  391. return prop[name];
  392. };
  393. /**
  394. * The function you call to draw the line chart
  395. *
  396. * @param bool An optional bool used internally to ditinguish whether the
  397. * line chart is being called by the bar chart
  398. *
  399. * Draw()
  400. * |
  401. * +--Draw()
  402. * | |
  403. * | +-DrawLine()
  404. * |
  405. * +-RedrawLine()
  406. * |
  407. * +-DrawCurvyLine()
  408. * |
  409. * +-DrawSpline()
  410. */
  411. this.draw =
  412. this.Draw = function ()
  413. {
  414. // MUST be the first thing done!
  415. if (typeof(prop['chart.background.image']) == 'string') {
  416. RG.DrawBackgroundImage(this);
  417. }
  418. /**
  419. * Fire the onbeforedraw event
  420. */
  421. RG.FireCustomEvent(this, 'onbeforedraw');
  422. /**
  423. * Parse the colors. This allows for simple gradient syntax
  424. */
  425. if (!this.colorsParsed) {
  426. this.parseColors();
  427. // Don't want to do this again
  428. this.colorsParsed = true;
  429. }
  430. /**
  431. * This is new in May 2011 and facilitates indiviual gutter settings,
  432. * eg chart.gutter.left
  433. */
  434. this.gutterLeft = prop['chart.gutter.left'];
  435. this.gutterRight = prop['chart.gutter.right'];
  436. this.gutterTop = prop['chart.gutter.top'];
  437. this.gutterBottom = prop['chart.gutter.bottom'];
  438. /**
  439. * Check for Chrome 6 and shadow
  440. *
  441. * TODO Remove once it's been fixed (for a while)
  442. * 07/03/2014 - Removed
  443. * 29/10/2011 - Looks like it's been fixed as long the linewidth is at least 1.01
  444. * SEARCH TAGS: CHROME FIX SHADOW BUG
  445. */
  446. //if ( prop['chart.shadow']
  447. // && RG.ISCHROME
  448. // && prop['chart.linewidth'] <= 1
  449. // && prop['chart.chromefix']
  450. // && prop['chart.shadow.blur'] > 0) {
  451. // alert('[RGRAPH WARNING] Chrome has a shadow bug, meaning you should increase the linewidth to at least 1.01');
  452. //}
  453. // Reset the data back to that which was initially supplied
  454. this.data = RG.array_clone(this.original_data);
  455. // Reset the max value
  456. this.max = 0;
  457. /**
  458. * Reverse the datasets so that the data and the labels tally
  459. * COMMENTED OUT 15TH AUGUST 2011
  460. */
  461. //this.data = RG.array_reverse(this.data);
  462. if (prop['chart.filled'] && !prop['chart.filled.range'] && this.data.length > 1 && prop['chart.filled.accumulative']) {
  463. var accumulation = [];
  464. for (var set=0; set<this.data.length; ++set) {
  465. for (var point=0; point<this.data[set].length; ++point) {
  466. this.data[set][point] = Number(accumulation[point] ? accumulation[point] : 0) + this.data[set][point];
  467. accumulation[point] = this.data[set][point];
  468. }
  469. }
  470. }
  471. /**
  472. * Get the maximum Y scale value
  473. */
  474. if (prop['chart.ymax']) {
  475. this.max = prop['chart.ymax'];
  476. this.min = prop['chart.ymin'] ? prop['chart.ymin'] : 0;
  477. this.scale2 = RG.getScale2(this, {
  478. 'max':this.max,
  479. 'min':prop['chart.ymin'],
  480. 'strict':true,
  481. 'scale.thousand':prop['chart.scale.thousand'],
  482. 'scale.point':prop['chart.scale.point'],
  483. 'scale.decimals':prop['chart.scale.decimals'],
  484. 'ylabels.count':prop['chart.ylabels.count'],
  485. 'scale.round':prop['chart.scale.round'],
  486. 'units.pre': prop['chart.units.pre'],
  487. 'units.post': prop['chart.units.post']
  488. });
  489. this.max = this.scale2.max ? this.scale2.max : 0;
  490. // Check for negative values
  491. if (!prop['chart.outofbounds']) {
  492. for (dataset=0; dataset<this.data.length; ++dataset) {
  493. for (var datapoint=0; datapoint<this.data[dataset].length; datapoint++) {
  494. // Check for negative values
  495. this.hasnegativevalues = (this.data[dataset][datapoint] < 0) || this.hasnegativevalues;
  496. }
  497. }
  498. }
  499. } else {
  500. this.min = prop['chart.ymin'] ? prop['chart.ymin'] : 0;
  501. // Work out the max Y value
  502. for (dataset=0; dataset<this.data.length; ++dataset) {
  503. for (var datapoint=0; datapoint<this.data[dataset].length; datapoint++) {
  504. this.max = Math.max(this.max, this.data[dataset][datapoint] ? Math.abs(parseFloat(this.data[dataset][datapoint])) : 0);
  505. // Check for negative values
  506. if (!prop['chart.outofbounds']) {
  507. this.hasnegativevalues = (this.data[dataset][datapoint] < 0) || this.hasnegativevalues;
  508. }
  509. }
  510. }
  511. this.scale2 = RG.getScale2(this, {
  512. 'max':this.max,
  513. 'min':prop['chart.ymin'],
  514. 'scale.thousand':prop['chart.scale.thousand'],
  515. 'scale.point':prop['chart.scale.point'],
  516. 'scale.decimals':prop['chart.scale.decimals'],
  517. 'ylabels.count':prop['chart.ylabels.count'],
  518. 'scale.round':prop['chart.scale.round'],
  519. 'units.pre': prop['chart.units.pre'],
  520. 'units.post': prop['chart.units.post']
  521. });
  522. this.max = this.scale2.max ? this.scale2.max : 0;
  523. }
  524. /**
  525. * Setup the context menu if required
  526. */
  527. if (prop['chart.contextmenu']) {
  528. RG.ShowContext(this);
  529. }
  530. /**
  531. * Reset the coords arrays otherwise it will keep growing
  532. */
  533. this.coords = [];
  534. this.coordsText = [];
  535. /**
  536. * Work out a few things. They need to be here because they depend on things you can change before you
  537. * call Draw() but after you instantiate the object
  538. */
  539. this.grapharea = ca.height - this.gutterTop - this.gutterBottom;
  540. this.halfgrapharea = this.grapharea / 2;
  541. this.halfTextHeight = prop['chart.text.size'] / 2;
  542. // Check the combination of the X axis position and if there any negative values
  543. //
  544. // 19th Dec 2010 - removed for Opera since it can be reported incorrectly whn there
  545. // are multiple graphs on the page
  546. if (prop['chart.xaxispos'] == 'bottom' && this.hasnegativevalues && !RG.ISOPERA) {
  547. alert('[LINE] You have negative values and the X axis is at the bottom. This is not good...');
  548. }
  549. if (prop['chart.variant'] == '3d') {
  550. RG.Draw3DAxes(this);
  551. }
  552. // Progressively Draw the chart
  553. RG.background.Draw(this);
  554. /**
  555. * Draw any horizontal bars that have been defined
  556. */
  557. if (prop['chart.background.hbars'] && prop['chart.background.hbars'].length > 0) {
  558. RG.DrawBars(this);
  559. }
  560. if (prop['chart.axesontop'] == false) {
  561. this.DrawAxes();
  562. }
  563. //if (typeof(shadowColor) == 'object') {
  564. // shadowColor = RG.array_reverse(RG.array_clone(prop['chart.shadow.color']]);
  565. //}
  566. /**
  567. * This facilitates the new Trace2 effect
  568. */
  569. co.save()
  570. co.beginPath();
  571. co.rect(0, 0, ca.width * prop['chart.animation.trace.clip'], ca.height);
  572. co.clip();
  573. for (var i=0, j=0, len=this.data.length; i<len; i++, j++) {
  574. co.beginPath();
  575. /**
  576. * Turn on the shadow if required
  577. */
  578. if (!prop['chart.filled']) {
  579. this.SetShadow(i);
  580. }
  581. /**
  582. * Draw the line
  583. */
  584. if (prop['chart.fillstyle']) {
  585. if (typeof(prop['chart.fillstyle']) == 'object' && prop['chart.fillstyle'][j]) {
  586. var fill = prop['chart.fillstyle'][j];
  587. } else if (typeof(prop['chart.fillstyle']) == 'object' && prop['chart.fillstyle'].toString().indexOf('Gradient') > 0) {
  588. var fill = prop['chart.fillstyle'];
  589. } else if (typeof(prop['chart.fillstyle']) == 'string') {
  590. var fill = prop['chart.fillstyle'];
  591. }
  592. } else if (prop['chart.filled']) {
  593. var fill = prop['chart.colors'][j];
  594. } else {
  595. var fill = null;
  596. }
  597. /**
  598. * Figure out the tickmark to use
  599. */
  600. if (prop['chart.tickmarks'] && typeof(prop['chart.tickmarks']) == 'object') {
  601. var tickmarks = prop['chart.tickmarks'][i];
  602. } else if (prop['chart.tickmarks'] && typeof(prop['chart.tickmarks']) == 'string') {
  603. var tickmarks = prop['chart.tickmarks'];
  604. } else if (prop['chart.tickmarks'] && typeof(prop['chart.tickmarks']) == 'function') {
  605. var tickmarks = prop['chart.tickmarks'];
  606. } else {
  607. var tickmarks = null;
  608. }
  609. this.DrawLine(this.data[i],
  610. prop['chart.colors'][j],
  611. fill,
  612. this.GetLineWidth(j),
  613. tickmarks,
  614. i);
  615. co.stroke();
  616. }
  617. /**
  618. * If the line is filled re-stroke the lines
  619. */
  620. if (prop['chart.filled'] && prop['chart.filled.accumulative'] && !prop['chart.curvy']) {
  621. for (var i=0; i<this.coords2.length; ++i) {
  622. co.beginPath();
  623. co.lineWidth = this.GetLineWidth(i);
  624. co.strokeStyle = prop['chart.colors'][i];
  625. for (var j=0,len=this.coords2[i].length; j<len; ++j) {
  626. if (j == 0 || this.coords2[i][j][1] == null || (this.coords2[i][j - 1] && this.coords2[i][j - 1][1] == null)) {
  627. co.moveTo(this.coords2[i][j][0], this.coords2[i][j][1]);
  628. } else {
  629. if (prop['chart.stepped']) {
  630. co.lineTo(this.coords2[i][j][0], this.coords2[i][j - 1][1]);
  631. }
  632. co.lineTo(this.coords2[i][j][0], this.coords2[i][j][1]);
  633. }
  634. }
  635. co.stroke();
  636. // No fill!
  637. }
  638. //Redraw the tickmarks
  639. if (prop['chart.tickmarks']) {
  640. co.beginPath();
  641. co.fillStyle = 'white';
  642. for (var i=0,len=this.coords2.length; i<len; ++i) {
  643. co.beginPath();
  644. co.strokeStyle = prop['chart.colors'][i];
  645. for (var j=0; j<this.coords2[i].length; ++j) {
  646. if (typeof(this.coords2[i][j]) == 'object' && typeof(this.coords2[i][j][0]) == 'number' && typeof(this.coords2[i][j][1]) == 'number') {
  647. var tickmarks = typeof(prop['chart.tickmarks']) == 'object' ? prop['chart.tickmarks'][i] : prop['chart.tickmarks'];
  648. this.DrawTick( this.coords2[i],
  649. this.coords2[i][j][0],
  650. this.coords2[i][j][1],
  651. co.strokeStyle,
  652. false,
  653. j == 0 ? 0 : this.coords2[i][j - 1][0],
  654. j == 0 ? 0 : this.coords2[i][j - 1][1],
  655. tickmarks,
  656. j);
  657. }
  658. }
  659. }
  660. co.stroke();
  661. co.fill();
  662. }
  663. } else if (prop['chart.filled'] && prop['chart.filled.accumulative'] && prop['chart.curvy']) {
  664. // Restroke the curvy filled accumulative lines
  665. for (var i=0; i<this.coordsSpline.length; i+=1) {
  666. co.beginPath();
  667. co.strokeStyle = prop['chart.colors'][i];
  668. co.lineWidth = this.GetLineWidth(i);
  669. for (var j=0,len=this.coordsSpline[i].length; j<len; j+=1) {
  670. var point = this.coordsSpline[i][j];
  671. j == 0 ? co.moveTo(point[0], point[1]) : co.lineTo(point[0], point[1]);
  672. }
  673. co.stroke();
  674. }
  675. for (var i=0,len=this.coords2.length; i<len; i+=1) {
  676. for (var j=0,len2=this.coords2[i].length; j<len2; ++j) {
  677. if (typeof(this.coords2[i][j]) == 'object' && typeof(this.coords2[i][j][0]) == 'number' && typeof(this.coords2[i][j][1]) == 'number') {
  678. var tickmarks = typeof prop['chart.tickmarks'] == 'object' && !RGraph.is_null(prop['chart.tickmarks']) ? prop['chart.tickmarks'][i] : prop['chart.tickmarks'];
  679. co.strokeStyle = prop['chart.colors'][i];
  680. this.DrawTick( this.coords2[i],
  681. this.coords2[i][j][0],
  682. this.coords2[i][j][1],
  683. prop['chart.colors'][i],
  684. false,
  685. j == 0 ? 0 : this.coords2[i][j - 1][0],
  686. j == 0 ? 0 : this.coords2[i][j - 1][1],
  687. tickmarks,
  688. j);
  689. }
  690. }
  691. }
  692. }
  693. co.restore();
  694. // ???
  695. co.beginPath();
  696. /**
  697. * If the axes have been requested to be on top, do that
  698. */
  699. if (prop['chart.axesontop']) {
  700. this.DrawAxes();
  701. }
  702. /**
  703. * Draw the labels
  704. */
  705. this.DrawLabels();
  706. /**
  707. * Draw the range if necessary
  708. */
  709. this.DrawRange();
  710. // Draw a key if necessary
  711. if (prop['chart.key'] && prop['chart.key'].length && RG.DrawKey) {
  712. RG.DrawKey(this, prop['chart.key'], prop['chart.colors']);
  713. }
  714. /**
  715. * Draw " above" labels if enabled
  716. */
  717. if (prop['chart.labels.above']) {
  718. this.DrawAboveLabels();
  719. }
  720. /**
  721. * Draw the "in graph" labels
  722. */
  723. RG.DrawInGraphLabels(this);
  724. /**
  725. * Redraw the lines if a filled range is on the cards
  726. */
  727. if (prop['chart.filled'] && prop['chart.filled.range'] && this.data.length == 2) {
  728. co.beginPath();
  729. var len = this.coords.length / 2;
  730. co.lineWidth = prop['chart.linewidth'];
  731. co.strokeStyle = prop['chart.colors'][0];
  732. for (var i=0; i<len; ++i) {
  733. if (!RG.is_null(this.coords[i][1])) {
  734. if (i == 0) {
  735. co.moveTo(this.coords[i][0], this.coords[i][1]);
  736. } else {
  737. co.lineTo(this.coords[i][0], this.coords[i][1]);
  738. }
  739. }
  740. }
  741. co.stroke();
  742. co.beginPath();
  743. if (prop['chart.colors'][1]) {
  744. co.strokeStyle = prop['chart.colors'][1];
  745. }
  746. for (var i=this.coords.length - 1; i>=len; --i) {
  747. if (!RG.is_null(this.coords[i][1])) {
  748. if (i == (this.coords.length - 1)) {
  749. co.moveTo(this.coords[i][0], this.coords[i][1]);
  750. } else {
  751. co.lineTo(this.coords[i][0], this.coords[i][1]);
  752. }
  753. }
  754. }
  755. co.stroke();
  756. } else if (prop['chart.filled'] && prop['chart.filled.range']) {
  757. alert('[LINE] You must have only two sets of data for a filled range chart');
  758. }
  759. /**
  760. * This function enables resizing
  761. */
  762. if (prop['chart.resizable']) {
  763. RG.AllowResizing(this);
  764. }
  765. /**
  766. * This installs the event listeners
  767. */
  768. RG.InstallEventListeners(this);
  769. /**
  770. * Fire the onfirstdraw event
  771. */
  772. if (this.firstDraw) {
  773. RG.fireCustomEvent(this, 'onfirstdraw');
  774. this.firstDrawFunc();
  775. this.firstDraw = false;
  776. }
  777. /**
  778. * Fire the RGraph ondraw event
  779. */
  780. RG.FireCustomEvent(this, 'ondraw');
  781. return this;
  782. };
  783. /**
  784. * Draws the axes
  785. */
  786. this.drawAxes =
  787. this.DrawAxes = function ()
  788. {
  789. //var RG = RGraph;
  790. //var ca = this.canvas;
  791. //var co = this.context;
  792. //var prop = this.properties;
  793. // Don't draw the axes?
  794. if (prop['chart.noaxes']) {
  795. return;
  796. }
  797. // Turn any shadow off
  798. RG.NoShadow(this);
  799. co.lineWidth = prop['chart.axis.linewidth'] + 0.001;
  800. co.lineCap = 'butt';
  801. co.strokeStyle = prop['chart.axis.color'];
  802. co.beginPath();
  803. // Draw the X axis
  804. if (prop['chart.noxaxis'] == false) {
  805. if (prop['chart.xaxispos'] == 'center') {
  806. co.moveTo(this.gutterLeft, Math.round((this.grapharea / 2) + this.gutterTop));
  807. co.lineTo(ca.width - this.gutterRight, Math.round((this.grapharea / 2) + this.gutterTop));
  808. } else if (prop['chart.xaxispos'] === 'top') {
  809. co.moveTo(this.gutterLeft, this.gutterTop);
  810. co.lineTo(ca.width - this.gutterRight, this.gutterTop);
  811. } else {
  812. co.moveTo(this.gutterLeft, ca.height - this.gutterBottom);
  813. co.lineTo(ca.width - this.gutterRight, ca.height - this.gutterBottom);
  814. }
  815. }
  816. // Draw the Y axis
  817. if (prop['chart.noyaxis'] == false) {
  818. if (prop['chart.yaxispos'] == 'left') {
  819. co.moveTo(this.gutterLeft, this.gutterTop);
  820. co.lineTo(this.gutterLeft, ca.height - this.gutterBottom);
  821. } else {
  822. co.moveTo(ca.width - this.gutterRight, this.gutterTop);
  823. co.lineTo(ca.width - this.gutterRight, ca.height - this.gutterBottom);
  824. }
  825. }
  826. /**
  827. * Draw the X tickmarks
  828. */
  829. if (prop['chart.noxaxis'] == false && prop['chart.numxticks'] > 0) {
  830. var xTickInterval = (ca.width - this.gutterLeft - this.gutterRight) / prop['chart.numxticks'];
  831. if (!xTickInterval || xTickInterval <= 0) {
  832. xTickInterval = (ca.width - this.gutterLeft - this.gutterRight) / (prop['chart.labels'] && prop['chart.labels'].length ? prop['chart.labels'].length - 1 : 10);
  833. }
  834. for (x=this.gutterLeft + (prop['chart.yaxispos'] == 'left' ? xTickInterval : 0); x<=(ca.width - this.gutterRight + 1 ); x+=xTickInterval) {
  835. if (prop['chart.yaxispos'] == 'right' && x >= (ca.width - this.gutterRight - 1) ) {
  836. break;
  837. }
  838. // If the last tick is not desired...
  839. if (prop['chart.noendxtick']) {
  840. if (prop['chart.yaxispos'] == 'left' && x >= (ca.width - this.gutterRight - 1)) {
  841. break;
  842. } else if (prop['chart.yaxispos'] == 'right' && x == this.gutterLeft) {
  843. continue;
  844. }
  845. }
  846. var yStart = prop['chart.xaxispos'] == 'center' ? (this.gutterTop + (this.grapharea / 2)) - 3 : ca.height - this.gutterBottom;
  847. var yEnd = prop['chart.xaxispos'] == 'center' ? yStart + 6 : ca.height - this.gutterBottom - (x % 60 == 0 ? prop['chart.largexticks'] * prop['chart.tickdirection'] : prop['chart.smallxticks'] * prop['chart.tickdirection']);
  848. if (prop['chart.xaxispos'] == 'center') {
  849. var yStart = Math.round((this.gutterTop + (this.grapharea / 2))) - 3;
  850. var yEnd = yStart + 6;
  851. } else if (prop['chart.xaxispos'] == 'bottom') {
  852. var yStart = ca.height - this.gutterBottom;
  853. var yEnd = ca.height - this.gutterBottom - (x % 60 == 0 ? prop['chart.largexticks'] * prop['chart.tickdirection'] : prop['chart.smallxticks'] * prop['chart.tickdirection']);
  854. yEnd += 0;
  855. } else if (prop['chart.xaxispos'] == 'top') {
  856. yStart = this.gutterTop - 3;
  857. yEnd = this.gutterTop;
  858. }
  859. co.moveTo(Math.round(x), yStart);
  860. co.lineTo(Math.round(x), yEnd);
  861. }
  862. // Draw an extra tickmark if there is no X axis, but there IS a Y axis
  863. } else if (prop['chart.noyaxis'] == false && prop['chart.numyticks'] > 0) {
  864. if (!prop['chart.noendytick']) {
  865. if (prop['chart.yaxispos'] == 'left') {
  866. co.moveTo(this.gutterLeft, Math.round(ca.height - this.gutterBottom));
  867. co.lineTo(this.gutterLeft - prop['chart.smallyticks'], Math.round(ca.height - this.gutterBottom));
  868. } else {
  869. co.moveTo(ca.width - this.gutterRight, Math.round(ca.height - this.gutterBottom));
  870. co.lineTo(ca.width - this.gutterRight + prop['chart.smallyticks'], Math.round(ca.height - this.gutterBottom));
  871. }
  872. }
  873. }
  874. /**
  875. * Draw the Y tickmarks
  876. */
  877. var numyticks = prop['chart.numyticks'];
  878. if (prop['chart.noyaxis'] == false && numyticks > 0) {
  879. var counter = 0;
  880. var adjustment = 0;
  881. if (prop['chart.yaxispos'] == 'right') {
  882. adjustment = (ca.width - this.gutterLeft - this.gutterRight);
  883. }
  884. // X axis at the center
  885. if (prop['chart.xaxispos'] == 'center') {
  886. var interval = (this.grapharea / numyticks);
  887. var lineto = (prop['chart.yaxispos'] == 'left' ? this.gutterLeft : ca.width - this.gutterRight + prop['chart.smallyticks']);
  888. // Draw the upper halves Y tick marks
  889. for (y=this.gutterTop; y<(this.grapharea / 2) + this.gutterTop; y+=interval) {
  890. if (y < (this.grapharea / 2) + this.gutterTop) {
  891. co.moveTo((prop['chart.yaxispos'] == 'left' ? this.gutterLeft - prop['chart.smallyticks'] : ca.width - this.gutterRight), Math.round(y));
  892. co.lineTo(lineto, Math.round(y));
  893. }
  894. }
  895. // Draw the lower halves Y tick marks
  896. for (y=this.gutterTop + (this.halfgrapharea) + interval; y <= this.grapharea + this.gutterTop; y+=interval) {
  897. co.moveTo((prop['chart.yaxispos'] == 'left' ? this.gutterLeft - prop['chart.smallyticks'] : ca.width - this.gutterRight), Math.round(y));
  898. co.lineTo(lineto, Math.round(y));
  899. }
  900. // X axis at the top
  901. } else if (prop['chart.xaxispos'] == 'top') {
  902. var interval = (this.grapharea / numyticks);
  903. var lineto = (prop['chart.yaxispos'] == 'left' ? this.gutterLeft : ca.width - this.gutterRight + prop['chart.smallyticks']);
  904. // Draw the Y tick marks
  905. for (y=this.gutterTop + interval; y <=this.grapharea + this.gutterTop; y+=interval) {
  906. co.moveTo((prop['chart.yaxispos'] == 'left' ? this.gutterLeft - prop['chart.smallyticks'] : ca.width - this.gutterRight), Math.round(y));
  907. co.lineTo(lineto, Math.round(y));
  908. }
  909. // If there's no X axis draw an extra tick
  910. if (prop['chart.noxaxis'] && prop['chart.noendytick'] == false) {
  911. co.moveTo((prop['chart.yaxispos'] == 'left' ? this.gutterLeft - prop['chart.smallyticks'] : ca.width - this.gutterRight), this.gutterTop);
  912. co.lineTo(lineto, this.gutterTop);
  913. }
  914. // X axis at the bottom
  915. } else {
  916. var lineto = (prop['chart.yaxispos'] == 'left' ? this.gutterLeft - prop['chart.smallyticks'] : ca.width - this.gutterRight + prop['chart.smallyticks']);
  917. for (y=this.gutterTop; y<(ca.height - this.gutterBottom) && counter < numyticks; y+=( (ca.height - this.gutterTop - this.gutterBottom) / numyticks) ) {
  918. co.moveTo(this.gutterLeft + adjustment, Math.round(y));
  919. co.lineTo(lineto, Math.round(y));
  920. var counter = counter + 1;
  921. }
  922. }
  923. // Draw an extra X tickmark
  924. } else if (prop['chart.noxaxis'] == false && prop['chart.numxticks'] > 0) {
  925. if (prop['chart.yaxispos'] == 'left') {
  926. co.moveTo(this.gutterLeft, prop['chart.xaxispos'] == 'top' ? this.gutterTop : ca.height - this.gutterBottom);
  927. co.lineTo(this.gutterLeft, prop['chart.xaxispos'] == 'top' ? this.gutterTop - prop['chart.smallxticks'] : ca.height - this.gutterBottom + prop['chart.smallxticks']);
  928. } else {
  929. co.moveTo(ca.width - this.gutterRight, ca.height - this.gutterBottom);
  930. co.lineTo(ca.width - this.gutterRight, ca.height - this.gutterBottom + prop['chart.smallxticks']);
  931. }
  932. }
  933. co.stroke();
  934. };
  935. /**
  936. * Draw the text labels for the axes
  937. */
  938. this.drawLabels =
  939. this.DrawLabels = function ()
  940. {
  941. co.strokeStyle = 'black';
  942. co.fillStyle = prop['chart.text.color'];
  943. co.lineWidth = 1;
  944. // Turn off any shadow
  945. RG.NoShadow(this);
  946. // This needs to be here
  947. var font = prop['chart.text.font'];
  948. var text_size = prop['chart.text.size'];
  949. var decimals = prop['chart.scale.decimals'];
  950. var context = co;
  951. var canvas = ca;
  952. var ymin = prop['chart.ymin'];
  953. // Draw the Y axis labels
  954. if (prop['chart.ylabels'] && prop['chart.ylabels.specific'] == null) {
  955. var units_pre = prop['chart.units.pre'];
  956. var units_post = prop['chart.units.post'];
  957. var xpos = prop['chart.yaxispos'] == 'left' ? this.gutterLeft - 5 : ca.width - this.gutterRight + 5;
  958. var align = prop['chart.yaxispos'] == 'left' ? 'right' : 'left';
  959. var numYLabels = this.scale2.labels.length;
  960. var bounding = false;
  961. var bgcolor = prop['chart.ylabels.inside'] ? prop['chart.ylabels.inside.color'] : null;
  962. /**
  963. * If the Y labels are inside the Y axis, invert the alignment
  964. */
  965. if (prop['chart.ylabels.inside'] == true && align == 'left') {
  966. xpos -= 10;
  967. align = 'right';
  968. bounding = true;
  969. } else if (prop['chart.ylabels.inside'] == true && align == 'right') {
  970. xpos += 10;
  971. align = 'left';
  972. bounding = true;
  973. }
  974. /**
  975. * X axis in the center
  976. */
  977. if (prop['chart.xaxispos'] == 'center') {
  978. var half = this.grapharea / 2;
  979. /**
  980. * Draw the top half
  981. */
  982. for (var i=0; i<this.scale2.labels.length; ++i) {
  983. RG.Text2(this, {'font': font,
  984. 'size': text_size,
  985. 'x': xpos,
  986. 'y': this.gutterTop + half - (((i+1)/numYLabels) * half),
  987. 'valign': 'center',
  988. 'halign':align,
  989. 'bounding': bounding,
  990. 'boundingFill': bgcolor,
  991. 'text': this.scale2.labels[i],
  992. 'tag': 'scale'
  993. });
  994. }
  995. /**
  996. * Draw the bottom half
  997. */
  998. for (var i=0; i<this.scale2.labels.length; ++i) {
  999. RG.Text2(this, {'font': font,
  1000. 'size': text_size,
  1001. 'x': xpos,
  1002. 'y': this.gutterTop + half + (((i+1)/numYLabels) * half),
  1003. 'valign': 'center',
  1004. 'halign':align,
  1005. 'bounding': bounding,
  1006. 'boundingFill': bgcolor,
  1007. 'text': '-' + this.scale2.labels[i],
  1008. 'tag': 'scale'
  1009. });
  1010. }
  1011. // No X axis - so draw 0
  1012. if (prop['chart.noxaxis'] == true || ymin != 0 || prop['chart.scale.zerostart']) {
  1013. RG.Text2(this,{'font':font,
  1014. 'size':text_size,
  1015. 'x':xpos,
  1016. 'y':this.gutterTop + half,
  1017. 'text':prop['chart.units.pre'] + ymin.toFixed(decimals) + prop['chart.units.post'],
  1018. 'bounding':bounding,
  1019. 'boundingFill':bgcolor,
  1020. 'valign':'center',
  1021. 'halign':align,
  1022. 'tag': 'scale'
  1023. });
  1024. }
  1025. /**
  1026. * X axis at the top
  1027. */
  1028. } else if (prop['chart.xaxispos'] == 'top') {
  1029. var half = this.grapharea / 2;
  1030. if (prop['chart.scale.invert']) {
  1031. for (var i=0; i<this.scale2.labels.length; ++i) {
  1032. RG.Text2(this, {'font': font,
  1033. 'size': text_size,
  1034. 'x': xpos,
  1035. 'y': this.gutterTop + ((i/this.scale2.labels.length) * this.grapharea),
  1036. 'valign': 'center',
  1037. 'halign':align,
  1038. 'bounding': bounding,
  1039. 'boundingFill': bgcolor,
  1040. 'text': '-' + this.scale2.labels[this.scale2.labels.length - (i+1)],
  1041. 'tag': 'scale'
  1042. });
  1043. }
  1044. } else {
  1045. for (var i=0; i<this.scale2.labels.length; ++i) {
  1046. RG.Text2(this, {'font': font,
  1047. 'size': text_size,
  1048. 'x': xpos,
  1049. 'y': this.gutterTop + (((i+1)/numYLabels) * this.grapharea),
  1050. 'valign': 'center',
  1051. 'halign':align,
  1052. 'bounding': bounding,
  1053. 'boundingFill': bgcolor,
  1054. 'text': '-' + this.scale2.labels[i],
  1055. 'tag': 'scale'
  1056. });
  1057. }
  1058. }
  1059. // Draw the lower limit if chart.ymin is specified
  1060. if ((prop['chart.ymin'] != 0 || prop['chart.noxaxis']) || prop['chart.scale.invert'] || prop['chart.scale.zerostart']) {
  1061. RG.Text2(this, {'font':font,
  1062. 'size':text_size,
  1063. 'x':xpos,
  1064. 'y': prop['chart.scale.invert'] ? ca.height - this.gutterBottom : this.gutterTop,
  1065. 'text': (prop['chart.ymin'] != 0 ? '-' : '') + RG.number_format(this, prop['chart.ymin'].toFixed(decimals), units_pre, units_post),
  1066. 'valign':'center',
  1067. 'halign': align,
  1068. 'bounding':bounding,
  1069. 'boundingFill':bgcolor,
  1070. 'tag': 'scale'});
  1071. }
  1072. /**
  1073. * X axis labels at the bottom
  1074. */
  1075. } else {
  1076. if (prop['chart.scale.invert']) {
  1077. // Draw the minimum value
  1078. RG.Text2(this, {'font': font,
  1079. 'size': text_size,
  1080. 'x': xpos,
  1081. 'y': this.gutterTop,
  1082. 'valign': 'center',
  1083. 'halign':align,
  1084. 'bounding': bounding,
  1085. 'boundingFill': bgcolor,
  1086. 'text': RG.number_format(this, this.min.toFixed(prop['chart.scale.decimals']), units_pre, units_post),
  1087. 'tag': 'scale'
  1088. });
  1089. for (var i=0,len=this.scale2.labels.length; i<len; ++i) {
  1090. RG.Text2(this, {'font': font,
  1091. 'size': text_size,
  1092. 'x': xpos,
  1093. 'y': this.gutterTop + (((i+1)/this.scale2.labels.length) * this.grapharea),
  1094. 'valign': 'center',
  1095. 'halign':align,
  1096. 'bounding': bounding,
  1097. 'boundingFill': bgcolor,
  1098. 'text': this.scale2.labels[i],
  1099. 'tag': 'scale'
  1100. });
  1101. }
  1102. } else {
  1103. for (var i=0,len=this.scale2.labels.length; i<len; ++i) {
  1104. RG.Text2(this, {'font': font,
  1105. 'size': text_size,
  1106. 'x': xpos,
  1107. 'y': this.gutterTop + ((i/this.scale2.labels.length) * this.grapharea),
  1108. 'valign': 'center',
  1109. 'halign':align,
  1110. 'bounding': bounding,
  1111. 'boundingFill': bgcolor,
  1112. 'text': this.scale2.labels[this.scale2.labels.length - (i + 1)],
  1113. 'tag': 'scale'
  1114. });
  1115. }
  1116. }
  1117. // Draw the lower limit if chart.ymin is specified
  1118. if ( (prop['chart.ymin']!= 0 && !prop['chart.scale.invert'] || prop['chart.scale.zerostart'])
  1119. || prop['chart.noxaxis']
  1120. ) {
  1121. RG.Text2(this, {'font':font,
  1122. 'size':text_size,
  1123. 'x':xpos,
  1124. 'y':prop['chart.scale.invert'] ? this.gutterTop : ca.height - this.gutterBottom,
  1125. 'text':RG.number_format(this, prop['chart.ymin'].toFixed(prop['chart.scale.decimals']), units_pre, units_post),
  1126. 'valign':'center',
  1127. 'halign':align,
  1128. 'bounding':bounding,
  1129. 'boundingFill':bgcolor,
  1130. 'tag': 'scale'
  1131. });
  1132. }
  1133. }
  1134. // No X axis - so draw 0 - but not if the X axis is in the center
  1135. if ( prop['chart.noxaxis'] == true
  1136. && prop['chart.ymin'] == null
  1137. && prop['chart.xaxispos'] != 'center'
  1138. && prop['chart.noendytick'] == false
  1139. ) {
  1140. RG.Text2(this, {'font':font,
  1141. 'size':text_size,
  1142. 'x':xpos,
  1143. 'y':prop['chart.xaxispos'] == 'top' ? this.gutterTop : (ca.height - this.gutterBottom),'text': prop['chart.units.pre'] + (0).toFixed(prop['chart.scale.decimals']) + prop['chart.units.post'],
  1144. 'valign':'center',
  1145. 'halign':align,
  1146. 'bounding':bounding,
  1147. 'boundingFill':bgcolor,
  1148. 'tag':'scale'
  1149. });
  1150. }
  1151. } else if (prop['chart.ylabels'] && typeof(prop['chart.ylabels.specific']) == 'object') {
  1152. // A few things
  1153. var gap = this.grapharea / prop['chart.ylabels.specific'].length;
  1154. var halign = prop['chart.yaxispos'] == 'left' ? 'right' : 'left';
  1155. var bounding = false;
  1156. var bgcolor = null;
  1157. var ymin = prop['chart.ymin'] != null && prop['chart.ymin'];
  1158. // Figure out the X coord based on the position of the axis
  1159. if (prop['chart.yaxispos'] == 'left') {
  1160. var x = this.gutterLeft - 5;
  1161. if (prop['chart.ylabels.inside']) {
  1162. x += 10;
  1163. halign = 'left';
  1164. bounding = true;
  1165. bgcolor = 'rgba(255,255,255,0.5)';
  1166. }
  1167. } else if (prop['chart.yaxispos'] == 'right') {
  1168. var x = ca.width - this.gutterRight + 5;
  1169. if (prop['chart.ylabels.inside']) {
  1170. x -= 10;
  1171. halign = 'right';
  1172. bounding = true;
  1173. bgcolor = 'rgba(255,255,255,0.5)';
  1174. }
  1175. }
  1176. // Draw the labels
  1177. if (prop['chart.xaxispos'] == 'center') {
  1178. // Draw the top halfs labels
  1179. for (var i=0; i<prop['chart.ylabels.specific'].length; ++i) {
  1180. var y = this.gutterTop + (this.grapharea / (((prop['chart.ylabels.specific'].length - 1)) * 2) * i);
  1181. if (ymin && ymin > 0) {
  1182. var y = ((this.grapharea / 2) / (prop['chart.ylabels.specific'].length - (ymin ? 1 : 0)) ) * i;
  1183. y += this.gutterTop;
  1184. }
  1185. RG.Text2(this, {'font':font,
  1186. 'size':text_size,
  1187. 'x':x,
  1188. 'y':y,
  1189. 'text':String(prop['chart.ylabels.specific'][i]),
  1190. 'valign': 'center',
  1191. 'halign':halign,
  1192. 'bounding':bounding,
  1193. 'boundingFill':bgcolor,
  1194. 'tag': 'ylabels.specific'
  1195. });
  1196. }
  1197. // Now reverse the labels and draw the bottom half
  1198. var reversed_labels = RG.array_reverse(prop['chart.ylabels.specific']);
  1199. // Draw the bottom halfs labels
  1200. for (var i=0; i<reversed_labels.length; ++i) {
  1201. var y = (this.grapharea / 2) + this.gutterTop + ((this.grapharea / ((reversed_labels.length - 1) * 2) ) * i);
  1202. RG.Text2(this, {'font':font,
  1203. 'size':text_size,
  1204. 'x':x,
  1205. 'y':y,
  1206. 'text':i == 0 ? '' : String(reversed_labels[i]),
  1207. 'valign': 'center',
  1208. 'halign':halign,
  1209. 'bounding':bounding,
  1210. 'boundingFill':bgcolor,
  1211. 'tag': 'ylabels.specific'
  1212. });
  1213. }
  1214. } else if (prop['chart.xaxispos'] == 'top') {
  1215. // Reverse the labels and draw
  1216. var reversed_labels = RG.array_reverse(prop['chart.ylabels.specific']);
  1217. // Draw the bottom halfs labels
  1218. for (var i=0; i<reversed_labels.length; ++i) {
  1219. var y = (this.grapharea / (reversed_labels.length - 1)) * i;
  1220. y = y + this.gutterTop;
  1221. RG.Text2(this, {'font':font,
  1222. 'size':text_size,
  1223. 'x':x,
  1224. 'y':y,
  1225. 'text':String(reversed_labels[i]),
  1226. 'valign': 'center',
  1227. 'halign':halign,
  1228. 'bounding':bounding,
  1229. 'boundingFill':bgcolor,
  1230. 'tag': 'ylabels.specific'
  1231. });
  1232. }
  1233. } else {
  1234. for (var i=0; i<prop['chart.ylabels.specific'].length; ++i) {
  1235. var y = this.gutterTop + ((this.grapharea / (prop['chart.ylabels.specific'].length - 1)) * i);
  1236. RG.Text2(this, {'font':font,
  1237. 'size':text_size,
  1238. 'x':x,
  1239. 'y':y,
  1240. 'text':String(prop['chart.ylabels.specific'][i]),
  1241. 'valign':'center',
  1242. 'halign':halign,
  1243. 'bounding':bounding,
  1244. 'boundingFill':bgcolor,
  1245. 'tag': 'ylabels.specific'
  1246. });
  1247. }
  1248. }
  1249. }
  1250. // Draw the X axis labels
  1251. if (prop['chart.labels'] && prop['chart.labels'].length > 0) {
  1252. var yOffset = 5;
  1253. var bordered = false;
  1254. var bgcolor = null;
  1255. /**
  1256. * Text angle
  1257. */
  1258. var angle = 0;
  1259. var valign = 'top';
  1260. var halign = 'center';
  1261. if (prop['chart.xlabels.inside']) {
  1262. yOffset = -5;
  1263. bordered = true;
  1264. bgcolor = prop['chart.xlabels.inside.color'];
  1265. valign = 'bottom';
  1266. }
  1267. if (prop['chart.xaxispos'] == 'top') {
  1268. valign = 'bottom';
  1269. yOffset += 2;
  1270. }
  1271. if (typeof(prop['chart.text.angle']) == 'number' && prop['chart.text.angle'] > 0) {
  1272. angle = -1 * prop['chart.text.angle'];
  1273. valign = 'center';
  1274. halign = 'right';
  1275. yOffset = 10;
  1276. if (prop['chart.xaxispos'] == 'top') {
  1277. yOffset = 10;
  1278. }
  1279. }
  1280. co.fillStyle = prop['chart.text.color'];
  1281. var numLabels = prop['chart.labels'].length;
  1282. for (i=0; i<numLabels; ++i) {
  1283. // Changed 8th Nov 2010 to be not reliant on the coords
  1284. //if (this.properties['chart.labels'][i] && this.coords && this.coords[i] && this.coords[i][0]) {
  1285. if (prop['chart.labels'][i]) {
  1286. var labelX = ((ca.width - this.gutterLeft - this.gutterRight - (2 * prop['chart.hmargin'])) / (numLabels - 1) ) * i;
  1287. labelX += this.gutterLeft + prop['chart.hmargin'];
  1288. /**
  1289. * Account for an unrelated number of labels
  1290. */
  1291. if (prop['chart.labels'].length != this.data[0].length) {
  1292. labelX = this.gutterLeft + prop['chart.hmargin'] + ((ca.width - this.gutterLeft - this.gutterRight - (2 * prop['chart.hmargin'])) * (i / (prop['chart.labels'].length - 1)));
  1293. }
  1294. // This accounts for there only being one point on the chart
  1295. if (!labelX) {
  1296. labelX = this.gutterLeft + prop['chart.hmargin'];
  1297. }
  1298. if (prop['chart.xaxispos'] == 'top' && prop['chart.text.angle'] > 0) {
  1299. halign = 'left';
  1300. }
  1301. if (prop['chart.text.angle'] != 0) {
  1302. halign = 'right';
  1303. }
  1304. RG.Text2(this, {'font':font,
  1305. 'size':text_size,
  1306. 'x':labelX,
  1307. 'y':(prop['chart.xaxispos'] == 'top') ? this.gutterTop - yOffset - (prop['chart.xlabels.inside'] ? -22 : 0) : (ca.height - this.gutterBottom) + yOffset,
  1308. 'text':String(prop['chart.labels'][i]),
  1309. 'valign':valign,
  1310. 'halign':halign,
  1311. 'bounding':bordered,
  1312. 'boundingFill':bgcolor,
  1313. 'angle':angle,
  1314. 'tag': 'labels'
  1315. });
  1316. }
  1317. }
  1318. }
  1319. co.stroke();
  1320. co.fill();
  1321. }
  1322. /**
  1323. * Draws the line
  1324. */
  1325. this.drawLine =
  1326. this.DrawLine = function (lineData, color, fill, linewidth, tickmarks, index)
  1327. {
  1328. // This facilitates the Rise animation (the Y value only)
  1329. if (prop['chart.animation.unfold.y'] && prop['chart.animation.factor'] != 1) {
  1330. for (var i=0; i<lineData.length; ++i) {
  1331. lineData[i] *= prop['chart.animation.factor'];
  1332. }
  1333. }
  1334. var penUp = false;
  1335. var yPos = null;
  1336. var xPos = 0;
  1337. co.lineWidth = 1;
  1338. var lineCoords = [];
  1339. /**
  1340. * Get the previous line data
  1341. */
  1342. if (index > 0) {
  1343. var prevLineCoords = this.coords2[index - 1];
  1344. }
  1345. // Work out the X interval
  1346. var xInterval = (ca.width - (2 * prop['chart.hmargin']) - this.gutterLeft - this.gutterRight) / (lineData.length - 1);
  1347. // Loop thru each value given, plotting the line
  1348. // (FORMERLY FIRST)
  1349. for (i=0,len=lineData.length; i<len; i+=1) {
  1350. var data_point = lineData[i];
  1351. /**
  1352. * Get the yPos for the given data point
  1353. */
  1354. var yPos = this.getYCoord(data_point);
  1355. // Null data points, and a special case for this bug:http://dev.rgraph.net/tests/ymin.html
  1356. if ( lineData[i] == null
  1357. || (prop['chart.xaxispos'] == 'bottom' && lineData[i] < this.min && !prop['chart.outofbounds'])
  1358. || (prop['chart.xaxispos'] == 'center' && lineData[i] < (-1 * this.max) && !prop['chart.outofbounds'])
  1359. || (((lineData[i] < this.min && prop['chart.xaxispos'] !== 'center') || lineData[i] > this.max) && !prop['chart.outofbounds'])) {
  1360. yPos = null;
  1361. }
  1362. // Not always very noticeable, but it does have an effect
  1363. // with thick lines
  1364. co.lineCap = 'round';
  1365. co.lineJoin = 'round';
  1366. // Plot the line if we're at least on the second iteration
  1367. if (i > 0) {
  1368. xPos = xPos + xInterval;
  1369. } else {
  1370. xPos = prop['chart.hmargin'] + this.gutterLeft;
  1371. }
  1372. if (prop['chart.animation.unfold.x']) {
  1373. xPos *= prop['chart.animation.factor'];
  1374. if (xPos < prop['chart.gutter.left']) {
  1375. xPos = prop['chart.gutter.left'];
  1376. }
  1377. }
  1378. /**
  1379. * Add the coords to an array
  1380. */
  1381. this.coords.push([xPos, yPos]);
  1382. lineCoords.push([xPos, yPos]);
  1383. }
  1384. co.stroke();
  1385. // Store the coords in another format, indexed by line number
  1386. this.coords2[index] = lineCoords;
  1387. /**
  1388. * For IE only: Draw the shadow ourselves as ExCanvas doesn't produce shadows
  1389. */
  1390. if (RG.ISOLD && prop['chart.shadow']) {
  1391. this.DrawIEShadow(lineCoords, co.shadowColor);
  1392. }
  1393. /**
  1394. * Now draw the actual line [FORMERLY SECOND]
  1395. */
  1396. co.beginPath();
  1397. // Transparent now as of 11/19/2011
  1398. co.strokeStyle = 'rgba(0,0,0,0)';
  1399. //co.strokeStyle = fill;
  1400. if (fill) {
  1401. co.fillStyle = fill;
  1402. }
  1403. var isStepped = prop['chart.stepped'];
  1404. var isFilled = prop['chart.filled'];
  1405. if (prop['chart.xaxispos'] == 'top') {
  1406. var xAxisPos = this.gutterTop;
  1407. } else if (prop['chart.xaxispos'] == 'center') {
  1408. var xAxisPos = this.gutterTop + (this.grapharea / 2);
  1409. } else if (prop['chart.xaxispos'] == 'bottom') {
  1410. var xAxisPos = ca.height - this.gutterBottom;
  1411. }
  1412. for (var i=0,len=lineCoords.length; i<len; i+=1) {
  1413. xPos = lineCoords[i][0];
  1414. yPos = lineCoords[i][1];
  1415. var set = index;
  1416. var prevY = (lineCoords[i - 1] ? lineCoords[i - 1][1] : null);
  1417. var isLast = (i + 1) == lineCoords.length;
  1418. /**
  1419. * This nullifys values which are out-of-range
  1420. */
  1421. if (!prop['chart.outofbounds'] && (prevY < this.gutterTop || prevY > (ca.height - this.gutterBottom) ) ) {
  1422. penUp = true;
  1423. }
  1424. if (i == 0 || penUp || !yPos || !prevY || prevY < this.gutterTop) {
  1425. if (prop['chart.filled'] && !prop['chart.filled.range']) {
  1426. if (!prop['chart.outofbounds'] || prevY === null || yPos === null) {
  1427. co.moveTo(xPos + 1, xAxisPos);
  1428. }
  1429. // This facilitates the X axis being at the top
  1430. // NOTE: Also done below
  1431. if (prop['chart.xaxispos'] == 'top') {
  1432. co.moveTo(xPos + 1, xAxisPos);
  1433. }
  1434. if (isStepped && i > 0) {
  1435. co.lineTo(xPos, lineCoords[i - 1][1]);
  1436. }
  1437. co.lineTo(xPos, yPos);
  1438. } else {
  1439. if (RG.ISOLD && yPos == null) {
  1440. // Nada
  1441. } else {
  1442. co.moveTo(xPos + 1, yPos);
  1443. }
  1444. }
  1445. if (yPos == null) {
  1446. penUp = true;
  1447. } else {
  1448. penUp = false;
  1449. }
  1450. } else {
  1451. // Draw the stepped part of stepped lines
  1452. if (isStepped) {
  1453. co.lineTo(xPos, lineCoords[i - 1][1]);
  1454. }
  1455. if ((yPos >= this.gutterTop && yPos <= (ca.height - this.gutterBottom)) || prop['chart.outofbounds'] ) {
  1456. if (isLast && prop['chart.filled'] && !prop['chart.filled.range'] && prop['chart.yaxispos'] == 'right') {
  1457. xPos -= 1;
  1458. }
  1459. // Added 8th September 2009
  1460. if (!isStepped || !isLast) {
  1461. co.lineTo(xPos, yPos);
  1462. if (isFilled && lineCoords[i+1] && lineCoords[i+1][1] == null) {
  1463. co.lineTo(xPos, xAxisPos);
  1464. }
  1465. // Added August 2010
  1466. } else if (isStepped && isLast) {
  1467. co.lineTo(xPos,yPos);
  1468. }
  1469. penUp = false;
  1470. } else {
  1471. penUp = true;
  1472. }
  1473. }
  1474. }
  1475. /**
  1476. * Draw a line to the X axis if the chart is filled
  1477. */
  1478. if (prop['chart.filled'] && !prop['chart.filled.range'] && !prop['chart.curvy']) {
  1479. // Is this needed ??
  1480. var fillStyle = prop['chart.fillstyle'];
  1481. /**
  1482. * Draw the bottom edge of the filled bit using either the X axis or the prevlinedata,
  1483. * depending on the index of the line. The first line uses the X axis, and subsequent
  1484. * lines use the prevLineCoords array
  1485. */
  1486. if (index > 0 && prop['chart.filled.accumulative']) {
  1487. co.lineTo(xPos, prevLineCoords ? prevLineCoords[i - 1][1] : (ca.height - this.gutterBottom - 1 + (prop['chart.xaxispos'] == 'center' ? (ca.height - this.gutterTop - this.gutterBottom) / 2 : 0)));
  1488. for (var k=(i - 1); k>=0; --k) {
  1489. co.lineTo(k == 0 ? prevLineCoords[k][0] + 1: prevLineCoords[k][0], prevLineCoords[k][1]);
  1490. }
  1491. } else {
  1492. // Draw a line down to the X axis
  1493. if (prop['chart.xaxispos'] == 'top') {
  1494. co.lineTo(xPos, prop['chart.gutter.top'] + 1);
  1495. co.lineTo(lineCoords[0][0],prop['chart.gutter.top'] + 1);
  1496. } else if (typeof(lineCoords[i - 1][1]) == 'number') {
  1497. var yPosition = prop['chart.xaxispos'] == 'center' ? ((ca.height - this.gutterTop - this.gutterBottom) / 2) + this.gutterTop : ca.height - this.gutterBottom;
  1498. co.lineTo(xPos,yPosition);
  1499. co.lineTo(lineCoords[0][0],yPosition);
  1500. }
  1501. }
  1502. co.fillStyle = fill;
  1503. co.fill();
  1504. co.beginPath();
  1505. }
  1506. /**
  1507. * FIXME this may need removing when Chrome is fixed
  1508. * SEARCH TAGS: CHROME SHADOW BUG
  1509. */
  1510. //if (false && RGraph.ISCHROME && prop['chart.shadow'] && prop['chart.chromefix'] && prop['chart.shadow.blur'] > 0) {
  1511. //
  1512. // for (var i=lineCoords.length - 1; i>=0; --i) {
  1513. // if (
  1514. // typeof(lineCoords[i][1]) != 'number'
  1515. // || (typeof(lineCoords[i+1]) == 'object' && typeof(lineCoords[i+1][1]) != 'number')
  1516. // ) {
  1517. // co.moveTo(lineCoords[i][0],lineCoords[i][1]);
  1518. // } else {
  1519. // co.lineTo(lineCoords[i][0],lineCoords[i][1]);
  1520. // }
  1521. // }
  1522. //}
  1523. co.stroke();
  1524. if (prop['chart.backdrop']) {
  1525. this.DrawBackdrop(lineCoords, color);
  1526. }
  1527. /**
  1528. * TODO CLIP TRACE
  1529. * By using the clip() method the Trace animation can be updated.
  1530. * NOTE: Needs to be done for the filled part as well
  1531. */
  1532. co.save();
  1533. co.beginPath();
  1534. co.rect(0,0,ca.width * prop['chart.animation.trace.clip'],ca.height);
  1535. co.clip();
  1536. // Now redraw the lines with the correct line width
  1537. this.SetShadow(index);
  1538. this.RedrawLine(lineCoords, color, linewidth, index);
  1539. co.stroke();
  1540. RG.NoShadow(this);
  1541. // Draw the tickmarks
  1542. for (var i=0; i<lineCoords.length; ++i) {
  1543. i = Number(i);
  1544. /**
  1545. * Set the color
  1546. */
  1547. co.strokeStyle = color;
  1548. if (isStepped && i == (lineCoords.length - 1)) {
  1549. co.beginPath();
  1550. //continue;
  1551. }
  1552. if (
  1553. (
  1554. tickmarks != 'endcircle'
  1555. && tickmarks != 'endsquare'
  1556. && tickmarks != 'filledendsquare'
  1557. && tickmarks != 'endtick'
  1558. && tickmarks != 'endtriangle'
  1559. && tickmarks != 'arrow'
  1560. && tickmarks != 'filledarrow'
  1561. )
  1562. || (i == 0 && tickmarks != 'arrow' && tickmarks != 'filledarrow')
  1563. || i == (lineCoords.length - 1)
  1564. ) {
  1565. var prevX = (i <= 0 ? null : lineCoords[i - 1][0]);
  1566. var prevY = (i <= 0 ? null : lineCoords[i - 1][1]);
  1567. this.DrawTick(lineData, lineCoords[i][0], lineCoords[i][1], color, false, prevX, prevY, tickmarks, i);
  1568. // Draws tickmarks on the stepped bits of stepped charts. Takend out 14th July 2010
  1569. //
  1570. //if (this.properties['chart.stepped'] && lineCoords[i + 1] && this.properties['chart.tickmarks'] != 'endsquare' && this.properties['chart.tickmarks'] != 'endcircle' && this.properties['chart.tickmarks'] != 'endtick') {
  1571. // this.DrawTick(lineCoords[i + 1][0], lineCoords[i][1], color);
  1572. //}
  1573. }
  1574. }
  1575. co.restore();
  1576. // Draw something off canvas to skirt an annoying bug
  1577. co.beginPath();
  1578. co.arc(ca.width + 50000, ca.height + 50000, 2, 0, 6.38, 1);
  1579. };
  1580. /**
  1581. * This functions draws a tick mark on the line
  1582. *
  1583. * @param xPos int The x position of the tickmark
  1584. * @param yPos int The y position of the tickmark
  1585. * @param color str The color of the tickmark
  1586. * @param bool Whether the tick is a shadow. If it is, it gets offset by the shadow offset
  1587. */
  1588. this.drawTick =
  1589. this.DrawTick = function (lineData, xPos, yPos, color, isShadow, prevX, prevY, tickmarks, index)
  1590. {
  1591. // Various conditions mean no tick
  1592. if (!prop['chart.line.visible']) {
  1593. return;
  1594. } else if (RG.is_null(yPos)) {
  1595. return false;
  1596. } else if ((yPos > (ca.height - this.gutterBottom)) && !prop['chart.outofbounds']) {
  1597. return;
  1598. } else if ((yPos < this.gutterTop) && !prop['chart.outofbounds']) {
  1599. return;
  1600. }
  1601. co.beginPath();
  1602. var offset = 0;
  1603. // Reset the stroke and lineWidth back to the same as what they were when the line was drawm
  1604. // UPDATE 28th July 2011 - the line width is now set to 1
  1605. co.lineWidth = prop['chart.tickmarks.linewidth'] ? prop['chart.tickmarks.linewidth'] : prop['chart.linewidth'];
  1606. co.strokeStyle = isShadow ? prop['chart.shadow.color'] : co.strokeStyle;
  1607. co.fillStyle = isShadow ? prop['chart.shadow.color'] : co.strokeStyle;
  1608. // Cicular tick marks
  1609. if ( tickmarks == 'circle'
  1610. || tickmarks == 'filledcircle'
  1611. || tickmarks == 'endcircle') {
  1612. if (tickmarks == 'circle'|| tickmarks == 'filledcircle' || (tickmarks == 'endcircle' && (index == 0 || index == (lineData.length - 1)))) {
  1613. co.beginPath();
  1614. co.arc(xPos + offset, yPos + offset, prop['chart.ticksize'], 0, 360 / (180 / RG.PI), false);
  1615. if (tickmarks == 'filledcircle') {
  1616. co.fillStyle = isShadow ? prop['chart.shadow.color'] : co.strokeStyle;
  1617. } else {
  1618. co.fillStyle = isShadow ? prop['chart.shadow.color'] : 'white';
  1619. }
  1620. co.stroke();
  1621. co.fill();
  1622. }
  1623. // Halfheight "Line" style tick marks
  1624. } else if (tickmarks == 'halftick') {
  1625. co.beginPath();
  1626. co.moveTo(Math.round(xPos), yPos);
  1627. co.lineTo(Math.round(xPos), yPos + prop['chart.ticksize']);
  1628. co.stroke();
  1629. // Tick style tickmarks
  1630. } else if (tickmarks == 'tick') {
  1631. co.beginPath();
  1632. co.moveTo(Math.round(xPos), yPos - prop['chart.ticksize']);
  1633. co.lineTo(Math.round(xPos), yPos + prop['chart.ticksize']);
  1634. co.stroke();
  1635. // Endtick style tickmarks
  1636. } else if (tickmarks == 'endtick' && (index == 0 || index == (lineData.length - 1))) {
  1637. co.beginPath();
  1638. co.moveTo(Math.round(xPos), yPos - prop['chart.ticksize']);
  1639. co.lineTo(Math.round(xPos), yPos + prop['chart.ticksize']);
  1640. co.stroke();
  1641. // "Cross" style tick marks
  1642. } else if (tickmarks == 'cross') {
  1643. co.beginPath();
  1644. var ticksize = prop['chart.ticksize'];
  1645. co.moveTo(xPos - ticksize, yPos - ticksize);
  1646. co.lineTo(xPos + ticksize, yPos + ticksize);
  1647. co.moveTo(xPos + ticksize, yPos - ticksize);
  1648. co.lineTo(xPos - ticksize, yPos + ticksize);
  1649. co.stroke();
  1650. // Triangle style tick marks
  1651. } else if (tickmarks == 'triangle' || tickmarks == 'filledtriangle' || (tickmarks == 'endtriangle' && (index == 0 || index == (lineData.length - 1)))) {
  1652. co.beginPath();
  1653. if (tickmarks == 'filledtriangle') {
  1654. co.fillStyle = isShadow ? prop['chart.shadow.color'] : co.strokeStyle;
  1655. } else {
  1656. co.fillStyle = 'white';
  1657. }
  1658. co.moveTo(Math.round(xPos - prop['chart.ticksize']), yPos + prop['chart.ticksize']);
  1659. co.lineTo(Math.round(xPos), yPos - prop['chart.ticksize']);
  1660. co.lineTo(Math.round(xPos + prop['chart.ticksize']), yPos + prop['chart.ticksize']);
  1661. co.closePath();
  1662. co.stroke();
  1663. co.fill();
  1664. // A white bordered circle
  1665. } else if (tickmarks == 'borderedcircle' || tickmarks == 'dot') {
  1666. co.lineWidth = 1;
  1667. co.strokeStyle = prop['chart.tickmarks.dot.color'];
  1668. co.fillStyle = prop['chart.tickmarks.dot.color'];
  1669. // The outer white circle
  1670. co.beginPath();
  1671. co.arc(xPos, yPos, prop['chart.ticksize'], 0, 360 / (180 / RG.PI), false);
  1672. co.closePath();
  1673. co.fill();
  1674. co.stroke();
  1675. // Now do the inners
  1676. co.beginPath();
  1677. co.fillStyle = color;
  1678. co.strokeStyle = color;
  1679. co.arc(xPos, yPos, prop['chart.ticksize'] - 2, 0, 360 / (180 / RG.PI), false);
  1680. co.closePath();
  1681. co.fill();
  1682. co.stroke();
  1683. } else if ( tickmarks == 'square'
  1684. || tickmarks == 'filledsquare'
  1685. || (tickmarks == 'endsquare' && (index == 0 || index == (lineData.length - 1)))
  1686. || (tickmarks == 'filledendsquare' && (index == 0 || index == (lineData.length - 1))) ) {
  1687. co.fillStyle = 'white';
  1688. co.strokeStyle = co.strokeStyle;
  1689. co.beginPath();
  1690. co.rect(Math.round(xPos - prop['chart.ticksize']), Math.round(yPos - prop['chart.ticksize']), prop['chart.ticksize'] * 2, prop['chart.ticksize'] * 2);
  1691. // Fillrect
  1692. if (tickmarks == 'filledsquare' || tickmarks == 'filledendsquare') {
  1693. co.fillStyle = isShadow ? prop['chart.shadow.color'] : co.strokeStyle;
  1694. co.rect(Math.round(xPos - prop['chart.ticksize']), Math.round(yPos - prop['chart.ticksize']), prop['chart.ticksize'] * 2, prop['chart.ticksize'] * 2);
  1695. } else if (tickmarks == 'square' || tickmarks == 'endsquare') {
  1696. co.fillStyle = isShadow ? prop['chart.shadow.color'] : 'white';
  1697. co.rect(Math.round((xPos - prop['chart.ticksize']) + 1), Math.round((yPos - prop['chart.ticksize']) + 1), (prop['chart.ticksize'] * 2) - 2, (prop['chart.ticksize'] * 2) - 2);
  1698. }
  1699. co.stroke();
  1700. co.fill();
  1701. /**
  1702. * FILLED arrowhead
  1703. */
  1704. } else if (tickmarks == 'filledarrow') {
  1705. var x = Math.abs(xPos - prevX);
  1706. var y = Math.abs(yPos - prevY);
  1707. if (yPos < prevY) {
  1708. var a = Math.atan(x / y) + 1.57;
  1709. } else {
  1710. var a = Math.atan(y / x) + 3.14;
  1711. }
  1712. co.beginPath();
  1713. co.moveTo(Math.round(xPos), Math.round(yPos));
  1714. co.arc(Math.round(xPos), Math.round(yPos), 7, a - 0.5, a + 0.5, false);
  1715. co.closePath();
  1716. co.stroke();
  1717. co.fill();
  1718. /**
  1719. * Arrow head, NOT filled
  1720. */
  1721. } else if (tickmarks == 'arrow') {
  1722. var orig_linewidth = co.lineWidth;
  1723. var x = Math.abs(xPos - prevX);
  1724. var y = Math.abs(yPos - prevY);
  1725. co.lineWidth;
  1726. if (yPos < prevY) {
  1727. var a = Math.atan(x / y) + 1.57;
  1728. } else {
  1729. var a = Math.atan(y / x) + 3.14;
  1730. }
  1731. co.beginPath();
  1732. co.moveTo(Math.round(xPos), Math.round(yPos));
  1733. co.arc(Math.round(xPos), Math.round(yPos), 7, a - 0.5 - (doc.all ? 0.1 : 0.01), a - 0.4, false);
  1734. co.moveTo(Math.round(xPos), Math.round(yPos));
  1735. co.arc(Math.round(xPos), Math.round(yPos), 7, a + 0.5 + (doc.all ? 0.1 : 0.01), a + 0.5, true);
  1736. co.stroke();
  1737. co.fill();
  1738. // Revert to original lineWidth
  1739. co.lineWidth = orig_linewidth;
  1740. /**
  1741. * Custom tick drawing function
  1742. */
  1743. } else if (typeof(tickmarks) == 'function') {
  1744. tickmarks(this, lineData, lineData[index], index, xPos, yPos, color, prevX, prevY);
  1745. }
  1746. };
  1747. /**
  1748. * Draws a filled range if necessary
  1749. */
  1750. this.drawRange =
  1751. this.DrawRange = function ()
  1752. {
  1753. //var RG = RGraph;
  1754. //var ca = this.canvas;
  1755. //var co = this.context;
  1756. //var prop = this.properties;
  1757. /**
  1758. * Fill the range if necessary
  1759. */
  1760. if (prop['chart.filled.range'] && prop['chart.filled'] && prop['chart.line.visible']) {
  1761. if (RG.is_null(prop['chart.filled.range.threshold'])) {
  1762. prop['chart.filled.range.threshold'] = this.ymin
  1763. prop['chart.filled.range.threshold.colors'] = [prop['chart.fillstyle'], prop['chart.fillstyle']]
  1764. }
  1765. for (var idx=0; idx<2; ++idx) {
  1766. var threshold_colors = prop['chart.filled.range.threshold.colors'];
  1767. var y = this.getYCoord(prop['chart.filled.range.threshold'])
  1768. co.save();
  1769. if (idx == 0) {
  1770. co.beginPath();
  1771. co.rect(0,0,ca.width,y);
  1772. co.clip();
  1773. } else {
  1774. co.beginPath();
  1775. co.rect(0,y,ca.width, ca.height);
  1776. co.clip();
  1777. }
  1778. co.beginPath();
  1779. co.fillStyle = (idx == 1 ? prop['chart.filled.range.threshold.colors'][1] : prop['chart.filled.range.threshold.colors'][0]);
  1780. //co.strokeStyle = prop['chart.fillstyle']; // Strokestyle not used now (10th October 2012)
  1781. co.lineWidth = 1;
  1782. var len = (this.coords.length / 2);
  1783. for (var i=0; i<len; ++i) {
  1784. if (!RG.is_null(this.coords[i][1])) {
  1785. if (i == 0) {
  1786. co.moveTo(this.coords[i][0], this.coords[i][1])
  1787. } else {
  1788. co.lineTo(this.coords[i][0], this.coords[i][1])
  1789. }
  1790. }
  1791. }
  1792. for (var i=this.coords.length - 1; i>=len; --i) {
  1793. if (RG.is_null(this.coords[i][1])) {
  1794. co.moveTo(this.coords[i][0], this.coords[i][1])
  1795. } else {
  1796. co.lineTo(this.coords[i][0], this.coords[i][1])
  1797. }
  1798. //co.lineTo(this.coords[i][0], this.coords[i][1])
  1799. }
  1800. // Taken out - 10th Oct 2012
  1801. //co.stroke();
  1802. co.fill();
  1803. co.restore();
  1804. }
  1805. }
  1806. };
  1807. /**
  1808. * Redraws the line with the correct line width etc
  1809. *
  1810. * @param array coords The coordinates of the line
  1811. */
  1812. this.redrawLine =
  1813. this.RedrawLine = function (coords, color, linewidth, index)
  1814. {
  1815. if (prop['chart.noredraw'] || prop['chart.filled.range']) {
  1816. return;
  1817. }
  1818. co.strokeStyle = (typeof(color) == 'object' && color && color.toString().indexOf('CanvasGradient') == -1 ? color[0] : color);
  1819. co.lineWidth = linewidth;
  1820. if (!prop['chart.line.visible']) {
  1821. co.strokeStyle = 'rgba(0,0,0,0)';
  1822. }
  1823. if (!RG.ISOLD && (prop['chart.curvy'] || prop['chart.spline'])) {
  1824. this.DrawCurvyLine(coords, !prop['chart.line.visible'] ? 'rgba(0,0,0,0)' : color, linewidth, index);
  1825. return;
  1826. }
  1827. co.beginPath();
  1828. var len = coords.length;
  1829. var width = ca.width
  1830. var height = ca.height;
  1831. var penUp = false;
  1832. for (var i=0; i<len; ++i) {
  1833. var xPos = coords[i][0];
  1834. var yPos = coords[i][1];
  1835. if (i > 0) {
  1836. var prevX = coords[i - 1][0];
  1837. var prevY = coords[i - 1][1];
  1838. }
  1839. if ((
  1840. (i == 0 && coords[i])
  1841. || (yPos < this.gutterTop)
  1842. || (prevY < this.gutterTop)
  1843. || (yPos > (height - this.gutterBottom))
  1844. || (i > 0 && prevX > (width - this.gutterRight))
  1845. || (i > 0 && prevY > (height - this.gutterBottom))
  1846. || prevY == null
  1847. || penUp == true
  1848. ) && (!prop['chart.outofbounds'] || yPos == null || prevY == null) ) {
  1849. if (RG.ISOLD && yPos == null) {
  1850. // ...?
  1851. } else {
  1852. co.moveTo(coords[i][0], coords[i][1]);
  1853. }
  1854. penUp = false;
  1855. } else {
  1856. if (prop['chart.stepped'] && i > 0) {
  1857. co.lineTo(coords[i][0], coords[i - 1][1]);
  1858. }
  1859. // Don't draw the last bit of a stepped chart. Now DO
  1860. //if (!this.properties['chart.stepped'] || i < (coords.length - 1)) {
  1861. co.lineTo(coords[i][0], coords[i][1]);
  1862. //}
  1863. penUp = false;
  1864. }
  1865. }
  1866. /**
  1867. * If two colors are specified instead of one, go over the up bits
  1868. */
  1869. if (prop['chart.colors.alternate'] && typeof(color) == 'object' && color[0] && color[1]) {
  1870. for (var i=1; i<len; ++i) {
  1871. var prevX = coords[i - 1][0];
  1872. var prevY = coords[i - 1][1];
  1873. if (prevY != null && coords[i][1] != null) {
  1874. co.beginPath();
  1875. co.strokeStyle = color[coords[i][1] < prevY ? 0 : 1];
  1876. co.lineWidth = prop['chart.linewidth'];
  1877. co.moveTo(prevX, prevY);
  1878. co.lineTo(coords[i][0], coords[i][1]);
  1879. co.stroke();
  1880. }
  1881. }
  1882. }
  1883. };
  1884. /**
  1885. * This function is used by MSIE only to manually draw the shadow
  1886. *
  1887. * @param array coords The coords for the line
  1888. */
  1889. this.drawIEShadow =
  1890. this.DrawIEShadow = function (coords, color)
  1891. {
  1892. var offsetx = prop['chart.shadow.offsetx'];
  1893. var offsety = prop['chart.shadow.offsety'];
  1894. co.lineWidth = prop['chart.linewidth'];
  1895. co.strokeStyle = color;
  1896. co.beginPath();
  1897. for (var i=0; i<coords.length; ++i) {
  1898. var isNull = RG.isNull(coords[i][1]);
  1899. var prevIsNull = RG.isNull(coords[i-1]) || RG.isNull(coords[i-1][1]);
  1900. if (i == 0 || isNull || prevIsNull) {
  1901. if (!isNull) {
  1902. co.moveTo(coords[i][0] + offsetx, coords[i][1] + offsety);
  1903. }
  1904. } else {
  1905. co.lineTo(coords[i][0] + offsetx, coords[i][1] + offsety);
  1906. }
  1907. }
  1908. co.stroke();
  1909. };
  1910. /**
  1911. * Draw the backdrop
  1912. */
  1913. this.drawBackdrop =
  1914. this.DrawBackdrop = function (coords, color)
  1915. {
  1916. //var ca = this.canvas;
  1917. //var co = this.context;
  1918. //var prop = this.properties;
  1919. var size = prop['chart.backdrop.size'];
  1920. co.lineWidth = size;
  1921. co.globalAlpha = prop['chart.backdrop.alpha'];
  1922. co.strokeStyle = color;
  1923. var yCoords = [];
  1924. co.beginPath();
  1925. if (prop['chart.curvy'] && !RG.ISOLD) {
  1926. // The DrawSpline function only takes the Y coords so extract them from the coords that have
  1927. // (which are X/Y pairs)
  1928. for (var i=0; i<coords.length; ++i) {
  1929. yCoords.push(coords[i][1])
  1930. }
  1931. this.DrawSpline(co, yCoords, color, null);
  1932. } else {
  1933. co.moveTo(coords[0][0], coords[0][1]);
  1934. for (var j=1; j<coords.length; ++j) {
  1935. co.lineTo(coords[j][0], coords[j][1]);
  1936. }
  1937. }
  1938. co.stroke();
  1939. // Reset the alpha value
  1940. co.globalAlpha = 1;
  1941. RG.NoShadow(this);
  1942. };
  1943. /**
  1944. * Returns the linewidth
  1945. */
  1946. this.getLineWidth =
  1947. this.GetLineWidth = function (i)
  1948. {
  1949. var linewidth = prop['chart.linewidth'];
  1950. if (typeof(linewidth) == 'number') {
  1951. return linewidth;
  1952. } else if (typeof(linewidth) == 'object') {
  1953. if (linewidth[i]) {
  1954. return linewidth[i];
  1955. } else {
  1956. return linewidth[0];
  1957. }
  1958. alert('[LINE] Error! chart.linewidth should be a single number or an array of one or more numbers');
  1959. }
  1960. };
  1961. /**
  1962. * The getPoint() method - used to get the point the mouse is currently over, if any
  1963. *
  1964. * @param object e The event object
  1965. * @param object OPTIONAL You can pass in the bar object instead of the
  1966. * function getting it from the canvas
  1967. */
  1968. this.getShape =
  1969. this.getPoint = function (e)
  1970. {
  1971. var obj = this;
  1972. var RG = RGraph;
  1973. var ca = canvas = e.target;
  1974. var co = context = this.context;
  1975. var prop = this.properties;
  1976. var mouseXY = RG.getMouseXY(e);
  1977. var mouseX = mouseXY[0];
  1978. var mouseY = mouseXY[1];
  1979. // This facilitates you being able to pass in the bar object as a parameter instead of
  1980. // the function getting it from the object
  1981. if (arguments[1]) {
  1982. obj = arguments[1];
  1983. }
  1984. for (var i=0; i<obj.coords.length; ++i) {
  1985. var x = obj.coords[i][0];
  1986. var y = obj.coords[i][1];
  1987. // Do this if the hotspot is triggered by the X coord AND the Y coord
  1988. if ( mouseX <= (x + prop['chart.tooltips.hotspot.size'])
  1989. && mouseX >= (x - prop['chart.tooltips.hotspot.size'])
  1990. && mouseY <= (y + prop['chart.tooltips.hotspot.size'])
  1991. && mouseY >= (y - prop['chart.tooltips.hotspot.size'])
  1992. ) {
  1993. if (RG.parseTooltipText) {
  1994. var tooltip = RG.parseTooltipText(prop['chart.tooltips'], i);
  1995. }
  1996. // Work out the dataset
  1997. var dataset = 0;
  1998. var idx = i;
  1999. while ((idx + 1) > this.data[dataset].length) {
  2000. idx -= this.data[dataset].length;
  2001. dataset++;
  2002. }
  2003. return {0:obj, 1:x, 2:y, 3:i, 'object': obj, 'x': x, 'y': y, 'index': i, 'tooltip': tooltip, 'dataset': dataset, 'index_adjusted': idx};
  2004. } else if ( prop['chart.tooltips.hotspot.xonly'] == true
  2005. && mouseX <= (x + prop['chart.tooltips.hotspot.size'])
  2006. && mouseX >= (x - prop['chart.tooltips.hotspot.size'])) {
  2007. var tooltip = RG.parseTooltipText(prop['chart.tooltips'], i);
  2008. return {0:obj, 1:x, 2:y, 3:i, 'object': obj, 'x': x, 'y': y, 'index': i, 'tooltip': tooltip};
  2009. }
  2010. }
  2011. };
  2012. /**
  2013. * Draws the above line labels
  2014. */
  2015. this.drawAboveLabels =
  2016. this.DrawAboveLabels = function ()
  2017. {
  2018. var size = prop['chart.labels.above.size'];
  2019. var font = prop['chart.text.font'];
  2020. var units_pre = prop['chart.units.pre'];
  2021. var units_post = prop['chart.units.post'];
  2022. var decimals = prop['chart.labels.above.decimals'];
  2023. // Use this to 'reset' the drawing state
  2024. co.beginPath();
  2025. // Don't need to check that chart.labels.above is enabled here, it's been done already
  2026. for (var i=0, len=this.coords.length; i<len; i+=1) {
  2027. var coords = this.coords[i];
  2028. RG.Text2(this, {'font':font,
  2029. 'size':size,
  2030. 'x':coords[0],
  2031. 'y':coords[1] - 5 - size,
  2032. 'text':RG.numberFormat(this, typeof decimals === 'number' ? this.data_arr[i].toFixed(decimals) : this.data_arr[i], units_pre, units_post),
  2033. 'valign':'center',
  2034. 'halign':'center',
  2035. 'bounding':true,
  2036. 'boundingFill':'white',
  2037. 'tag': 'labels.above'
  2038. });
  2039. }
  2040. };
  2041. /**
  2042. * Draw a curvy line.
  2043. */
  2044. this.drawCurvyLine =
  2045. this.DrawCurvyLine = function (coords, color, linewidth, index)
  2046. {
  2047. //var ca = this.canvas;
  2048. //var co = this.context;
  2049. //var prop = this.properties;
  2050. if (RG.ISOLD) {
  2051. return;
  2052. }
  2053. var yCoords = [];
  2054. for (var i=0; i<coords.length; ++i) {
  2055. yCoords.push(coords[i][1])
  2056. }
  2057. if (prop['chart.filled']) {
  2058. co.beginPath();
  2059. co.moveTo(coords[0][0],ca.height - this.gutterBottom);
  2060. this.DrawSpline(co, yCoords, color, index);
  2061. if (prop['chart.filled.accumulative'] && index > 0) {
  2062. for (var i=(this.coordsSpline[index - 1].length - 1); i>=0; i-=1) {
  2063. co.lineTo(this.coordsSpline[index - 1][i][0], this.coordsSpline[index - 1][i][1]);
  2064. }
  2065. } else {
  2066. co.lineTo(coords[coords.length - 1][0],ca.height - this.gutterBottom);
  2067. }
  2068. co.fill();
  2069. }
  2070. co.beginPath();
  2071. this.DrawSpline(co, yCoords, color, index);
  2072. co.stroke();
  2073. };
  2074. /**
  2075. * When you click on the chart, this method can return the Y value at that point. It works for any point on the
  2076. * chart (that is inside the gutters) - not just points on the Line.
  2077. *
  2078. * @param object e The event object
  2079. */
  2080. this.getValue = function (arg)
  2081. {
  2082. if (arg.length == 2) {
  2083. var mouseX = arg[0];
  2084. var mouseY = arg[1];
  2085. } else {
  2086. var mouseCoords = RG.getMouseXY(arg);
  2087. var mouseX = mouseCoords[0];
  2088. var mouseY = mouseCoords[1];
  2089. }
  2090. var obj = this;
  2091. var xaxispos = prop['chart.xaxispos'];
  2092. if (mouseY < prop['chart.gutter.top']) {
  2093. return xaxispos == 'bottom' || xaxispos == 'center' ? this.max : this.min;
  2094. } else if (mouseY > (ca.height - prop['chart.gutter.bottom'])) {
  2095. return xaxispos == 'bottom' ? this.min : this.max;
  2096. }
  2097. if (prop['chart.xaxispos'] == 'center') {
  2098. var value = (( (obj.grapharea / 2) - (mouseY - prop['chart.gutter.top'])) / obj.grapharea) * (obj.max - obj.min);
  2099. value *= 2;
  2100. value > 0 ? value += this.min : value -= this.min;
  2101. return value;
  2102. } else if (prop['chart.xaxispos'] == 'top') {
  2103. var value = ((obj.grapharea - (mouseY - prop['chart.gutter.top'])) / obj.grapharea) * (obj.max - obj.min);
  2104. value = Math.abs(obj.max - value) * -1;
  2105. return value;
  2106. } else {
  2107. var value = ((obj.grapharea - (mouseY - prop['chart.gutter.top'])) / obj.grapharea) * (obj.max - obj.min)
  2108. value += obj.min;
  2109. return value;
  2110. }
  2111. };
  2112. /**
  2113. * Each object type has its own Highlight() function which highlights the appropriate shape
  2114. *
  2115. * @param object shape The shape to highlight
  2116. */
  2117. this.highlight =
  2118. this.Highlight = function (shape)
  2119. {
  2120. if (prop['chart.tooltips.highlight']) {
  2121. // Add the new highlight
  2122. RG.Highlight.Point(this, shape);
  2123. }
  2124. };
  2125. /**
  2126. * The getObjectByXY() worker method. Don't call this call:
  2127. *
  2128. * RG.ObjectRegistry.getObjectByXY(e)
  2129. *
  2130. * @param object e The event object
  2131. */
  2132. this.getObjectByXY = function (e)
  2133. {
  2134. //var ca = this.canvas;
  2135. //var prop = this.properties;
  2136. var mouseXY = RG.getMouseXY(e);
  2137. // The 5 is so that the cursor doesn't have to be over the graphArea to trigger the hotspot
  2138. if (
  2139. (mouseXY[0] > prop['chart.gutter.left'] - 5)
  2140. && mouseXY[0] < (ca.width - prop['chart.gutter.right'] + 5)
  2141. && mouseXY[1] > (prop['chart.gutter.top'] - 5)
  2142. && mouseXY[1] < (ca.height - prop['chart.gutter.bottom'] + 5)
  2143. ) {
  2144. return this;
  2145. }
  2146. };
  2147. /**
  2148. * This method handles the adjusting calculation for when the mouse is moved
  2149. *
  2150. * @param object e The event object
  2151. */
  2152. this.adjusting_mousemove =
  2153. this.Adjusting_mousemove = function (e)
  2154. {
  2155. /**
  2156. * Handle adjusting for the Bar
  2157. */
  2158. if (prop['chart.adjustable'] && RG.Registry.Get('chart.adjusting') && RG.Registry.Get('chart.adjusting').uid == this.uid) {
  2159. // Rounding the value to the given number of decimals make the chart step
  2160. var value = Number(this.getValue(e));//.toFixed(this.properties['chart.scale.decimals']);
  2161. var shape = RG.Registry.Get('chart.adjusting.shape');
  2162. if (shape) {
  2163. RG.Registry.Set('chart.adjusting.shape', shape);
  2164. this.original_data[shape['dataset']][shape['index_adjusted']] = Number(value);
  2165. RG.redrawCanvas(e.target);
  2166. RG.fireCustomEvent(this, 'onadjust');
  2167. }
  2168. }
  2169. };
  2170. /**
  2171. * This function can be used when the canvas is clicked on (or similar - depending on the event)
  2172. * to retrieve the relevant Y coordinate for a particular value.
  2173. *
  2174. * @param int value The value to get the Y coordinate for
  2175. */
  2176. this.getYCoord = function (value)
  2177. {
  2178. if (typeof(value) != 'number') {
  2179. return null;
  2180. }
  2181. var y;
  2182. var xaxispos = prop['chart.xaxispos'];
  2183. // Higher than max
  2184. // Commented out on March 7th 2013 because the tan curve was not showing correctly
  2185. //if (value > this.max) {
  2186. // value = this.max;
  2187. //}
  2188. if (xaxispos == 'top') {
  2189. // Account for negative numbers
  2190. //if (value < 0) {
  2191. // value = Math.abs(value);
  2192. //}
  2193. y = ((value - this.min) / (this.max - this.min)) * this.grapharea;
  2194. // Inverted Y labels
  2195. if (prop['chart.scale.invert']) {
  2196. y = this.grapharea - y;
  2197. }
  2198. y = y + this.gutterTop
  2199. } else if (xaxispos == 'center') {
  2200. y = ((value - this.min) / (this.max - this.min)) * (this.grapharea / 2);
  2201. y = (this.grapharea / 2) - y;
  2202. y += this.gutterTop;
  2203. } else {
  2204. if ((value < this.min || value > this.max) && prop['chart.outofbounds'] == false) {
  2205. return null;
  2206. }
  2207. y = ((value - this.min) / (this.max - this.min)) * this.grapharea;
  2208. // Inverted Y labels
  2209. if (prop['chart.scale.invert']) {
  2210. y = this.grapharea - y;
  2211. }
  2212. y = ca.height - this.gutterBottom - y;
  2213. }
  2214. return y;
  2215. };
  2216. /**
  2217. * This function positions a tooltip when it is displayed
  2218. *
  2219. * @param obj object The chart object
  2220. * @param int x The X coordinate specified for the tooltip
  2221. * @param int y The Y coordinate specified for the tooltip
  2222. * @param objec tooltip The tooltips DIV element
  2223. */
  2224. this.positionTooltip = function (obj, x, y, tooltip, idx)
  2225. {
  2226. //var ca = obj.canvas;
  2227. //var co = obj.context;
  2228. //var prop = obj.properties;
  2229. var coordX = obj.coords[tooltip.__index__][0];
  2230. var coordY = obj.coords[tooltip.__index__][1];
  2231. var canvasXY = RG.getCanvasXY(obj.canvas);
  2232. var gutterLeft = prop['chart.gutter.left'];
  2233. var gutterTop = prop['chart.gutter.top'];
  2234. var width = tooltip.offsetWidth;
  2235. // Set the top position
  2236. tooltip.style.left = 0;
  2237. tooltip.style.top = parseInt(tooltip.style.top) - 9 + 'px';
  2238. // By default any overflow is hidden
  2239. tooltip.style.overflow = '';
  2240. // The arrow
  2241. var img = new Image();
  2242. img.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAAFCAYAAACjKgd3AAAARUlEQVQYV2NkQAN79+797+RkhC4M5+/bd47B2dmZEVkBCgcmgcsgbAaA9GA1BCSBbhAuA/AagmwQPgMIGgIzCD0M0AMMAEFVIAa6UQgcAAAAAElFTkSuQmCC';
  2243. img.style.position = 'absolute';
  2244. img.id = '__rgraph_tooltip_pointer__';
  2245. img.style.top = (tooltip.offsetHeight - 2) + 'px';
  2246. tooltip.appendChild(img);
  2247. // Reposition the tooltip if at the edges:
  2248. // LEFT edge
  2249. if ((canvasXY[0] + coordX - (width / 2)) < 10) {
  2250. tooltip.style.left = (canvasXY[0] + coordX - (width * 0.2)) + 'px';
  2251. img.style.left = ((width * 0.2) - 8.5) + 'px';
  2252. // RIGHT edge
  2253. } else if ((canvasXY[0] + coordX + (width / 2)) > doc.body.offsetWidth) {
  2254. tooltip.style.left = canvasXY[0] + coordX - (width * 0.8) + 'px';
  2255. img.style.left = ((width * 0.8) - 8.5) + 'px';
  2256. // Default positioning - CENTERED
  2257. } else {
  2258. tooltip.style.left = (canvasXY[0] + coordX - (width * 0.5)) + 'px';
  2259. img.style.left = ((width * 0.5) - 8.5) + 'px';
  2260. }
  2261. };
  2262. /**
  2263. * This function draws a curvy line
  2264. *
  2265. * @param object context The 2D context
  2266. * @param array coords The coordinates
  2267. */
  2268. this.drawSpline =
  2269. this.DrawSpline = function (context, coords, color, index)
  2270. {
  2271. // Commented out on 26th Oct 2013
  2272. //var co = context;
  2273. //var ca = co.canvas;
  2274. //var prop = this.properties;
  2275. this.coordsSpline[index] = [];
  2276. var xCoords = [];
  2277. var gutterLeft = prop['chart.gutter.left'];
  2278. var gutterRight = prop['chart.gutter.right'];
  2279. var hmargin = prop['chart.hmargin'];
  2280. var interval = (ca.width - (gutterLeft + gutterRight) - (2 * hmargin)) / (coords.length - 1);
  2281. co.strokeStyle = color;
  2282. /**
  2283. * The drawSpline function takes an array of JUST Y coords - not X/Y coords. So the line coords need converting
  2284. * if we've been given X/Y pairs
  2285. */
  2286. for (var i=0,len=coords.length; i<len;i+=1) {
  2287. if (typeof coords[i] == 'object' && coords[i] && coords[i].length == 2) {
  2288. coords[i] = Number(coords[i][1]);
  2289. }
  2290. }
  2291. /**
  2292. * Get the Points array in the format we want - first value should be null along with the lst value
  2293. */
  2294. var P = [coords[0]];
  2295. for (var i=0; i<coords.length; ++i) {
  2296. P.push(coords[i]);
  2297. }
  2298. P.push(coords[coords.length - 1] + (coords[coords.length - 1] - coords[coords.length - 2]));
  2299. //P.push(null);
  2300. for (var j=1; j<P.length-2; ++j) {
  2301. for (var t=0; t<10; ++t) {
  2302. var yCoord = Spline( t/10, P[j-1], P[j], P[j+1], P[j+2] );
  2303. xCoords.push(((j-1) * interval) + (t * (interval / 10)) + gutterLeft + hmargin);
  2304. co.lineTo(xCoords[xCoords.length - 1], yCoord);
  2305. if (typeof index == 'number') {
  2306. this.coordsSpline[index].push([xCoords[xCoords.length - 1], yCoord]);
  2307. }
  2308. }
  2309. }
  2310. // Draw the last section
  2311. co.lineTo(((j-1) * interval) + gutterLeft + hmargin, P[j]);
  2312. if (typeof index == 'number') {
  2313. this.coordsSpline[index].push([((j-1) * interval) + gutterLeft + hmargin, P[j]]);
  2314. }
  2315. function Spline (t, P0, P1, P2, P3)
  2316. {
  2317. return 0.5 * ((2 * P1) +
  2318. ((0-P0) + P2) * t +
  2319. ((2*P0 - (5*P1) + (4*P2) - P3) * (t*t) +
  2320. ((0-P0) + (3*P1)- (3*P2) + P3) * (t*t*t)));
  2321. }
  2322. };
  2323. /**
  2324. * This allows for easy specification of gradients
  2325. */
  2326. this.parseColors = function ()
  2327. {
  2328. // Save the original colors so that they can be restored when the canvas is reset
  2329. if (this.original_colors.length === 0) {
  2330. this.original_colors['chart.colors'] = RGraph.array_clone(prop['chart.colors']);
  2331. this.original_colors['chart.fillstyle'] = RGraph.array_clone(prop['chart.fillstyle']);
  2332. this.original_colors['chart.key.colors'] = RGraph.array_clone(prop['chart.key.colors']);
  2333. this.original_colors['chart.background.barcolor1'] = prop['chart.background.barcolor1'];
  2334. this.original_colors['chart.background.barcolor2'] = prop['chart.background.barcolor2'];
  2335. this.original_colors['chart.background.grid.color'] = prop['chart.background.grid.color'];
  2336. this.original_colors['chart.background.color'] = prop['chart.background.color'];
  2337. this.original_colors['chart.text.color'] = prop['chart.text.color'];
  2338. this.original_colors['chart.crosshairs.color'] = prop['chart.crosshairs.color'];
  2339. this.original_colors['chart.annotate.color'] = prop['chart.annotate.color'];
  2340. this.original_colors['chart.title.color'] = prop['chart.title.color'];
  2341. this.original_colors['chart.title.yaxis.color'] = prop['chart.title.yaxis.color'];
  2342. this.original_colors['chart.key.background'] = prop['chart.key.background'];
  2343. this.original_colors['chart.axis.color'] = prop['chart.axis.color'];
  2344. this.original_colors['chart.highlight.fill'] = prop['chart.highlight.fill'];
  2345. }
  2346. for (var i=0; i<prop['chart.colors'].length; ++i) {
  2347. if (typeof(prop['chart.colors'][i]) == 'object' && prop['chart.colors'][i][0] && prop['chart.colors'][i][1]) {
  2348. prop['chart.colors'][i][0] = this.parseSingleColorForGradient(prop['chart.colors'][i][0]);
  2349. prop['chart.colors'][i][1] = this.parseSingleColorForGradient(prop['chart.colors'][i][1]);
  2350. } else {
  2351. prop['chart.colors'][i] = this.parseSingleColorForGradient(prop['chart.colors'][i]);
  2352. }
  2353. }
  2354. /**
  2355. * Fillstyle
  2356. */
  2357. if (prop['chart.fillstyle']) {
  2358. if (typeof(prop['chart.fillstyle']) == 'string') {
  2359. prop['chart.fillstyle'] = this.parseSingleColorForGradient(prop['chart.fillstyle'], 'vertical');
  2360. } else {
  2361. for (var i=0; i<prop['chart.fillstyle'].length; ++i) {
  2362. prop['chart.fillstyle'][i] = this.parseSingleColorForGradient(prop['chart.fillstyle'][i], 'vertical');
  2363. }
  2364. }
  2365. }
  2366. /**
  2367. * Key colors
  2368. */
  2369. if (!RG.is_null(prop['chart.key.colors'])) {
  2370. for (var i=0; i<prop['chart.key.colors'].length; ++i) {
  2371. prop['chart.key.colors'][i] = this.parseSingleColorForGradient(prop['chart.key.colors'][i]);
  2372. }
  2373. }
  2374. /**
  2375. * Parse various properties for colors
  2376. */
  2377. var properties = ['chart.background.barcolor1',
  2378. 'chart.background.barcolor2',
  2379. 'chart.background.grid.color',
  2380. 'chart.background.color',
  2381. 'chart.text.color',
  2382. 'chart.crosshairs.color',
  2383. 'chart.annotate.color',
  2384. 'chart.title.color',
  2385. 'chart.title.yaxis.color',
  2386. 'chart.key.background',
  2387. 'chart.axis.color',
  2388. 'chart.highlight.fill'];
  2389. for (var i=0; i<properties.length; ++i) {
  2390. prop[properties[i]] = this.parseSingleColorForGradient(prop[properties[i]]);
  2391. }
  2392. };
  2393. /**
  2394. * This parses a single color value
  2395. */
  2396. this.parseSingleColorForGradient = function (color)
  2397. {
  2398. //var RG = RGraph;
  2399. //var ca = this.canvas;
  2400. //var co = this.context;
  2401. if (!color || typeof(color) != 'string') {
  2402. return color;
  2403. }
  2404. /**
  2405. * Horizontal or vertical gradient
  2406. */
  2407. var dir = typeof(arguments[1]) == 'string' ? arguments[1] : 'vertical';
  2408. if (typeof color === 'string' && color.match(/^gradient\((.*)\)$/i)) {
  2409. var parts = RegExp.$1.split(':');
  2410. // Create the gradient
  2411. if (dir == 'horizontal') {
  2412. var grad = co.createLinearGradient(0,0,ca.width,0);
  2413. } else {
  2414. var grad = co.createLinearGradient(0,ca.height - prop['chart.gutter.bottom'],0,prop['chart.gutter.top']);
  2415. }
  2416. var diff = 1 / (parts.length - 1);
  2417. grad.addColorStop(0, RG.trim(parts[0]));
  2418. for (var j=1; j<parts.length; ++j) {
  2419. grad.addColorStop(j * diff, RG.trim(parts[j]));
  2420. }
  2421. }
  2422. return grad ? grad : color;
  2423. };
  2424. /**
  2425. * Sets the appropriate shadow
  2426. */
  2427. this.setShadow =
  2428. this.SetShadow = function (i)
  2429. {
  2430. //var ca = this.canvas;
  2431. //var co = this.context;
  2432. //var prop = this.properties;
  2433. if (prop['chart.shadow']) {
  2434. /**
  2435. * Handle the appropriate shadow color. This now facilitates an array of differing
  2436. * shadow colors
  2437. */
  2438. var shadowColor = prop['chart.shadow.color'];
  2439. /**
  2440. * Accommodate an array of shadow colors as well as a single string
  2441. */
  2442. if (typeof(shadowColor) == 'object' && shadowColor[i - 1]) {
  2443. co.shadowColor = shadowColor[i];
  2444. } else if (typeof(shadowColor) == 'object') {
  2445. co.shadowColor = shadowColor[0];
  2446. } else if (typeof(shadowColor) == 'string') {
  2447. co.shadowColor = shadowColor;
  2448. }
  2449. co.shadowBlur = prop['chart.shadow.blur'];
  2450. co.shadowOffsetX = prop['chart.shadow.offsetx'];
  2451. co.shadowOffsetY = prop['chart.shadow.offsety'];
  2452. }
  2453. };
  2454. /**
  2455. * This function handles highlighting an entire data-series for the interactive
  2456. * key
  2457. *
  2458. * @param int index The index of the data series to be highlighted
  2459. */
  2460. this.interactiveKeyHighlight = function (index)
  2461. {
  2462. var coords = this.coords2[index];
  2463. if (coords) {
  2464. var pre_linewidth = co.lineWidth;
  2465. var pre_linecap = co.lineCap;
  2466. co.lineWidth = prop['chart.linewidth'] + 10;
  2467. co.lineCap = 'round';
  2468. co.strokeStyle = prop['chart.key.interactive.highlight.chart.stroke'];
  2469. co.beginPath();
  2470. if (prop['chart.curvy']) {
  2471. this.DrawSpline(co, coords, prop['chart.key.interactive.highlight.chart'], null);
  2472. } else {
  2473. for (var i=0,len=coords.length; i<len; i+=1) {
  2474. if ( i == 0
  2475. || RG.is_null(coords[i][1])
  2476. || (typeof coords[i - 1][1] != undefined && RG.is_null(coords[i - 1][1]))) {
  2477. co.moveTo(coords[i][0], coords[i][1]);
  2478. } else {
  2479. co.lineTo(coords[i][0], coords[i][1]);
  2480. }
  2481. }
  2482. }
  2483. co.stroke();
  2484. // Reset the lineCap and lineWidth
  2485. co.lineWidth = pre_linewidth;
  2486. co.lineCap = pre_linecap;
  2487. }
  2488. };
  2489. /**
  2490. * Using a function to add events makes it easier to facilitate method chaining
  2491. *
  2492. * @param string type The type of even to add
  2493. * @param function func
  2494. */
  2495. this.on = function (type, func)
  2496. {
  2497. if (type.substr(0,2) !== 'on') {
  2498. type = 'on' + type;
  2499. }
  2500. this[type] = func;
  2501. return this;
  2502. };
  2503. /**
  2504. * This function runs once only
  2505. * (put at the end of the file (before any effects))
  2506. */
  2507. this.firstDrawFunc = function ()
  2508. {
  2509. };
  2510. /**
  2511. * Trace
  2512. *
  2513. * This effect is for the Line chart, uses the jQuery library and slowly
  2514. * uncovers the Line , but you can see the background of the chart. This effect
  2515. * is quite new (1/10/2011) and as such should be used with caution.
  2516. *
  2517. * @param object An object of configuration. You can give 'duration' or 'frames' here.
  2518. * @param function An optional callback function
  2519. */
  2520. this.trace = function ()
  2521. {
  2522. var obj = this;
  2523. var callback = typeof arguments[1] === 'function' ? arguments[1] : function () {};
  2524. var opt = arguments[0] || {};
  2525. if (opt.frames) {
  2526. opt.duration = (opt.frames / 60) * 1000;
  2527. }
  2528. if (!opt.duration) {
  2529. opt.duration = 1500;
  2530. }
  2531. RG.clear(obj.canvas);
  2532. RG.redrawCanvas(obj.canvas);
  2533. /**
  2534. * Create the DIV that the second canvas will sit in
  2535. */
  2536. var div = doc.createElement('DIV');
  2537. var xy = RG.getCanvasXY(obj.canvas);
  2538. div.id = '__rgraph_trace_animation_' + RG.random(0, 4351623) + '__';
  2539. div.style.left = xy[0] + 'px';
  2540. div.style.top = xy[1] + 'px';
  2541. div.style.width = obj.Get('chart.gutter.left');
  2542. div.style.height = obj.canvas.height + 'px';
  2543. div.style.position = 'absolute';
  2544. div.style.overflow = 'hidden';
  2545. doc.body.appendChild(div);
  2546. obj.canvas.__rgraph_trace_div__ = div;
  2547. /**
  2548. * Make the second canvas
  2549. */
  2550. var id = '__rgraph_line_trace_animation_' + RG.random(0, 99999999) + '__';
  2551. var canvas2 = doc.createElement('CANVAS');
  2552. // Copy the 3D CSS transformation properties across from the original canvas
  2553. var properties = ['WebkitTransform','MozTransform','OTransform','MSTransform','transform'];
  2554. for (i in properties) {
  2555. var name = properties[i];
  2556. if (typeof obj.canvas.style[name] === 'string' && obj.canvas.style[name]) {
  2557. canvas2.style[name] = obj.canvas.style[name];
  2558. }
  2559. }
  2560. obj.canvas.__rgraph_line_canvas2__ = canvas2;
  2561. canvas2.width = obj.canvas.width;
  2562. canvas2.height = obj.canvas.height;
  2563. canvas2.style.position = 'absolute';
  2564. canvas2.style.left = 0;
  2565. canvas2.style.top = 0;
  2566. // This stops the clear effect clearing the canvas - which can happen if you have multiple canvas tags on the page all with
  2567. // dynamic effects that do redrawing
  2568. canvas2.noclear = true;
  2569. canvas2.id = id;
  2570. div.appendChild(canvas2);
  2571. var reposition_canvas2 = function (e)
  2572. {
  2573. var xy = RG.getCanvasXY(obj.canvas);
  2574. div.style.left = xy[0] + 'px';
  2575. div.style.top = xy[1] + 'px';
  2576. }
  2577. window.addEventListener('resize', reposition_canvas2, false)
  2578. /**
  2579. * Make a copy of the original Line object
  2580. */
  2581. var obj2 = new RG.Line(id, RG.array_clone(obj.original_data));
  2582. // Remove the new line from the ObjectRegistry so that it isn't redawn
  2583. RG.ObjectRegistry.Remove(obj2);
  2584. for (i in obj.properties) {
  2585. if (typeof i === 'string') {
  2586. obj2.Set(i, obj.properties[i]);
  2587. }
  2588. }
  2589. //obj2.Set('chart.tooltips', null);
  2590. obj2.Set('labels', []);
  2591. obj2.Set('background.grid', false);
  2592. obj2.Set('background.barcolor1', 'rgba(0,0,0,0)');
  2593. obj2.Set('background.barcolor2', 'rgba(0,0,0,0)');
  2594. obj2.Set('ylabels', false);
  2595. obj2.Set('noaxes', true);
  2596. obj2.Set('title', '');
  2597. obj2.Set('title.xaxis', '');
  2598. obj2.Set('title.yaxis', '');
  2599. obj2.Set('filled.accumulative', obj.Get('chart.filled.accumulative'));
  2600. obj.Set('key', []);
  2601. obj2.Draw();
  2602. obj.canvas.__rgraph_trace_obj2__ = obj2;
  2603. /**
  2604. * This effectively hides the line
  2605. */
  2606. obj.Set('line.visible', false);
  2607. obj.Set('colors', ['rgba(0,0,0,0)']);
  2608. if (obj.Get('filled')) {
  2609. var original_fillstyle = obj.Get('chart.fillstyle');
  2610. obj.Set('fillstyle', 'rgba(0,0,0,0)');
  2611. obj.Set('animation.trace.original.fillstyle', original_fillstyle);
  2612. }
  2613. RG.clear(obj.canvas);
  2614. //obj.Draw();
  2615. RG.redrawCanvas(obj.canvas);
  2616. /**
  2617. * Place a DIV over the canvas to stop interaction with it
  2618. */
  2619. if (!obj.canvas.__rgraph_trace_cover__) {
  2620. var div2 = doc.createElement('DIV');
  2621. div2.id = '__rgraph_trace_animation_' + RG.random(0, 4351623) + '__';
  2622. div2.style.left = xy[0] + 'px';
  2623. div2.style.top = xy[1] + 'px';
  2624. div2.style.width = obj.canvas.width + 'px';
  2625. div2.style.height = obj.canvas.height + 'px';
  2626. div2.style.position = 'absolute';
  2627. div2.style.overflow = 'hidden';
  2628. div2.style.backgroundColor = 'rgba(0,0,0,0)';
  2629. div.div2 = div2;
  2630. obj.canvas.__rgraph_trace_cover__ = div2;
  2631. doc.body.appendChild(div2);
  2632. } else {
  2633. div2 = obj.canvas.__rgraph_trace_cover__;
  2634. }
  2635. /**
  2636. * Get rid of the second canvas and turn the line back on
  2637. * on the original.
  2638. */
  2639. trace_complete = function (obj)
  2640. {
  2641. var obj2 = obj.canvas.__rgraph_trace_obj2__;
  2642. // Remove the window resize listener
  2643. win.removeEventListener('resize', reposition_canvas2, false);
  2644. div.style.display = 'none';
  2645. div2.style.display = 'none';
  2646. //div.removeChild(canvas2);
  2647. obj.Set('line.visible', true);
  2648. // Revert the filled status back to as it was
  2649. obj.Set('filled', RGraph.array_clone(obj2.Get('chart.filled')));
  2650. obj.Set('fillstyle', obj.Get('chart.animation.trace.original.fillstyle'));
  2651. obj.Set('colors', RGraph.array_clone(obj2.Get('chart.colors')));
  2652. obj.Set('key', RGraph.array_clone(obj2.Get('chart.key')));
  2653. RGraph.RedrawCanvas(obj.canvas);
  2654. obj.canvas.__rgraph_trace_div__.style.display = 'none';
  2655. obj.canvas.__rgraph_line_canvas2__.style.display = 'none';
  2656. obj.canvas.__rgraph_trace_cover__.style.display = 'none';
  2657. obj.canvas.__rgraph_trace_div__ = null;
  2658. obj.canvas.__rgraph_line_canvas2__ = null;
  2659. obj.canvas.__rgraph_trace_cover__ = null;
  2660. callback(obj);
  2661. };
  2662. /**
  2663. * Animate the DIV that contains the canvas
  2664. */
  2665. jq('#' + div.id).animate({
  2666. width: obj.canvas.width - obj.gutterRight + 'px'
  2667. }, opt.duration, function () {trace_complete(obj)});
  2668. return this;
  2669. };
  2670. /**
  2671. * Unfold
  2672. *
  2673. * This effect gradually increases the X/Y coordinatesfrom 0
  2674. *
  2675. * @param object obj The chart object
  2676. */
  2677. this.unfold = function ()
  2678. {
  2679. var obj = this;
  2680. var opt = arguments[0] ? arguments[0] : {};
  2681. var frames = opt.frames ? opt.frames : 30;
  2682. var frame = 0;
  2683. var callback = arguments[1] ? arguments[1] : function () {};
  2684. var initial = prop['chart.animation.unfold.initial'];
  2685. prop['chart.animation.factor'] = prop['chart.animation.unfold.initial'];
  2686. function iterator ()
  2687. {
  2688. prop['chart.animation.factor'] = ((1 - initial) * (frame / frames)) + initial;
  2689. RG.clear(obj.canvas);
  2690. RG.redrawCanvas(obj.canvas);
  2691. if (frame < frames) {
  2692. frame++;
  2693. RG.Effects.updateCanvas(iterator);
  2694. } else {
  2695. callback(obj);
  2696. }
  2697. }
  2698. iterator();
  2699. return this;
  2700. };
  2701. /**
  2702. * Trace2
  2703. *
  2704. * This is a new version of the Trace effect which no longer requires jQuery and is more compatible
  2705. * with other effects (eg Expand). This new effect is considerably simpler and less code.
  2706. *
  2707. * @param object Options for the effect. Currently only "frames" is available.
  2708. * @param int A function that is called when the ffect is complete
  2709. */
  2710. this.trace2 = function ()
  2711. {
  2712. var obj = this;
  2713. var callback = arguments[2];
  2714. var opt = arguments[0] || {};
  2715. var frames = opt.frames || 30;
  2716. var frame = 0;
  2717. var callback = arguments[1] || function () {};
  2718. obj.Set('animation.trace.clip', 0);
  2719. function iterator ()
  2720. {
  2721. RG.clear(obj.canvas);
  2722. RG.redrawCanvas(obj.canvas);
  2723. if (frame++ < frames) {
  2724. obj.Set('animation.trace.clip', frame / frames);
  2725. RG.Effects.updateCanvas(iterator);
  2726. } else {
  2727. callback(obj);
  2728. }
  2729. }
  2730. iterator();
  2731. return this;
  2732. };
  2733. /**
  2734. * FoldToCenter
  2735. *
  2736. * Line chart FoldTocenter
  2737. *
  2738. * @param object OPTIONAL An object map of options
  2739. * @param function OPTIONAL A callback to run when the effect is complete
  2740. */
  2741. this.foldtocenter =
  2742. this.foldToCenter = function ()
  2743. {
  2744. var obj = this;
  2745. var opt = arguments[0] || {};
  2746. var frames = opt.frames || 30;
  2747. var frame = 0;
  2748. var callback = arguments[1] || function () {};
  2749. var center_value = obj.scale2.max / 2;
  2750. obj.Set('chart.ymax', obj.scale2.max);
  2751. var original_data = RG.array_clone(obj.original_data);
  2752. function iterator ()
  2753. {
  2754. for (var i=0,len=obj.data.length; i<len; ++i) {
  2755. if (obj.data[i].length) {
  2756. for (var j=0,len2=obj.data[i].length; j<len2; ++j) {
  2757. var dataset = obj.original_data[i];
  2758. if (dataset[j] > center_value) {
  2759. dataset[j] = original_data[i][j] - ((original_data[i][j] - center_value) * (frame / frames));
  2760. } else {
  2761. dataset[j] = original_data[i][j] + (((center_value - original_data[i][j]) / frames) * frame);
  2762. }
  2763. }
  2764. }
  2765. }
  2766. RG.clear(obj.canvas);
  2767. RG.redrawCanvas(obj.canvas)
  2768. if (frame++ < frames) {
  2769. RG.Effects.updateCanvas(iterator);
  2770. } else {
  2771. callback(obj);
  2772. }
  2773. }
  2774. iterator();
  2775. return this;
  2776. };
  2777. /**
  2778. * UnfoldFromCenterTrace effect
  2779. *
  2780. * @param object An object containing options
  2781. * @param function A callback function
  2782. */
  2783. this.unfoldFromCenterTrace =
  2784. this.unfoldFromCenterTrace2 = function ()
  2785. {
  2786. var obj = this;
  2787. var opt = arguments[0] || {};
  2788. var frames = opt.frames || 30;
  2789. var frame = 0;
  2790. var data = RG.array_clone(obj.original_data);
  2791. var callback = arguments[1] || function () {};
  2792. // Draw the chart once to get the scale values
  2793. obj.canvas.style.visibility = 'hidden';
  2794. obj.Draw();
  2795. var max = obj.scale2.max;
  2796. RG.clear(obj.canvas);
  2797. obj.canvas.style.visibility = 'visible';
  2798. /**
  2799. * When the Trace function finishes it calls this function
  2800. */
  2801. var unfoldCallback = function ()
  2802. {
  2803. obj.original_data = data;
  2804. obj.unfoldFromCenter({frames: frames / 2}, callback);
  2805. };
  2806. /**
  2807. * Determine the mid-point
  2808. */
  2809. var half = obj.Get('chart.xaxispos') == 'center' ? obj.min : ((obj.max - obj.min) / 2) + obj.min;
  2810. obj.Set('chart.ymax', obj.max);
  2811. for (var i=0,len=obj.original_data.length; i<len; ++i) {
  2812. for (var j=0; j<obj.original_data[i].length; ++j) {
  2813. obj.original_data[i][j] = (obj.Get('chart.filled') && obj.Get('chart.filled.accumulative') && i > 0) ? 0 : half;
  2814. }
  2815. }
  2816. RG.clear(obj.canvas);
  2817. obj.trace2({frames: frames / 2}, unfoldCallback);
  2818. };
  2819. /**
  2820. * UnfoldFromCenter
  2821. *
  2822. * Line chart unfold from center
  2823. *
  2824. * @param object An option map of properties. Only frames is supported: {frames: 30}
  2825. * @param function An optional callback
  2826. */
  2827. this.unfoldFromCenter = function ()
  2828. {
  2829. var obj = this;
  2830. var opt = arguments[0] || {};
  2831. var frames = opt.frames || 30;
  2832. var frame = 0;
  2833. var callback = arguments[1] || function () {};
  2834. // Draw the chart once to get the scale values
  2835. obj.canvas.style.visibility = 'hidden';
  2836. obj.Draw();
  2837. var max = obj.scale2.max;
  2838. RG.clear(obj.canvas);
  2839. obj.canvas.style.visibility = 'visible';
  2840. var center_value = obj.Get('chart.xaxispos') === 'center' ? prop['chart.ymin'] : ((obj.max - obj.min) / 2) + obj.min;
  2841. var original_data = RG.array_clone(obj.original_data);
  2842. var steps = null;
  2843. obj.Set('chart.ymax', max);
  2844. if (!steps) {
  2845. steps = [];
  2846. for (var dataset=0,len=original_data.length; dataset<len; ++dataset) {
  2847. steps[dataset] = []
  2848. for (var i=0,len2=original_data[dataset].length; i<len2; ++i) {
  2849. if (prop['chart.filled'] && prop['chart.filled.accumulative'] && dataset > 0) {
  2850. steps[dataset][i] = original_data[dataset][i] / frames;
  2851. obj.original_data[dataset][i] = center_value;
  2852. } else {
  2853. steps[dataset][i] = (original_data[dataset][i] - center_value) / frames;
  2854. obj.original_data[dataset][i] = center_value;
  2855. }
  2856. }
  2857. }
  2858. }
  2859. function unfoldFromCenter ()
  2860. {
  2861. for (var dataset=0; dataset<original_data.length; ++dataset) {
  2862. for (var i=0; i<original_data[dataset].length; ++i) {
  2863. obj.original_data[dataset][i] += steps[dataset][i];
  2864. }
  2865. }
  2866. RG.clear(obj.canvas);
  2867. RG.redrawCanvas(obj.canvas);
  2868. if (--frames > 0) {
  2869. RG.Effects.updateCanvas(unfoldFromCenter);
  2870. } else {
  2871. obj.original_data = RG.array_clone(original_data);
  2872. RG.clear(obj.canvas);
  2873. RG.redrawCanvas(obj.canvas);
  2874. callback(obj);
  2875. }
  2876. }
  2877. unfoldFromCenter();
  2878. return this;
  2879. };
  2880. /**
  2881. * Register the object so it is redrawn when necessary
  2882. */
  2883. RG.Register(this);
  2884. };