RGraph.line.js 122 KB

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