RGraph.scatter.js 112 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828
  1. // version: 2014-06-26
  2. /**
  3. * o--------------------------------------------------------------------------------o
  4. * | This file is part of the RGraph package. RGraph is Free Software, licensed |
  5. * | under the MIT license - so it's free to use for all purposes. If you want to |
  6. * | donate to help keep the project going then you can do so here: |
  7. * | |
  8. * | http://www.rgraph.net/donate |
  9. * o--------------------------------------------------------------------------------o
  10. */
  11. RGraph = window.RGraph || {isRGraph: true};
  12. /**
  13. * The scatter graph constructor
  14. *
  15. * @param object canvas The cxanvas object
  16. * @param array data The chart data
  17. */
  18. RGraph.Scatter = function (id, data)
  19. {
  20. var tmp = RGraph.getCanvasTag(id);
  21. // Get the canvas and context objects
  22. this.id = tmp[0];
  23. this.canvas = tmp[1];
  24. this.canvas.__object__ = this;
  25. this.context = this.canvas.getContext ? this.canvas.getContext("2d", {alpha: (typeof id === 'object' && id.alpha === false) ? false : true}) : null;
  26. this.max = 0;
  27. this.coords = [];
  28. this.data = [];
  29. this.type = 'scatter';
  30. this.isRGraph = true;
  31. this.uid = RGraph.CreateUID();
  32. this.canvas.uid = this.canvas.uid ? this.canvas.uid : RGraph.CreateUID();
  33. this.colorsParsed = false;
  34. this.coordsText = [];
  35. this.original_colors = [];
  36. this.firstDraw = true; // After the first draw this will be false
  37. /**
  38. * Compatibility with older browsers
  39. */
  40. //RGraph.OldBrowserCompat(this.context);
  41. // Various config properties
  42. this.properties = {
  43. 'chart.background.barcolor1': 'rgba(0,0,0,0)',
  44. 'chart.background.barcolor2': 'rgba(0,0,0,0)',
  45. 'chart.background.grid': true,
  46. 'chart.background.grid.width': 1,
  47. 'chart.background.grid.color': '#ddd',
  48. 'chart.background.grid.hsize': 20,
  49. 'chart.background.grid.vsize': 20,
  50. 'chart.background.hbars': null,
  51. 'chart.background.vbars': null,
  52. 'chart.background.grid.vlines': true,
  53. 'chart.background.grid.hlines': true,
  54. 'chart.background.grid.border': true,
  55. 'chart.background.grid.autofit':true,
  56. 'chart.background.grid.autofit.numhlines': 5,
  57. 'chart.background.grid.autofit.numvlines': 20,
  58. 'chart.background.image': null,
  59. 'chart.background.image.stretch': true,
  60. 'chart.background.image.x': null,
  61. 'chart.background.image.y': null,
  62. 'chart.background.image.w': null,
  63. 'chart.background.image.h': null,
  64. 'chart.background.image.align': null,
  65. 'chart.background.color': null,
  66. 'chart.text.size': 10,
  67. 'chart.text.angle': 0,
  68. 'chart.text.color': 'black',
  69. 'chart.text.font': 'Arial',
  70. 'chart.tooltips': [], // Default must be an empty array
  71. 'chart.tooltips.effect': 'fade',
  72. 'chart.tooltips.event': 'onmousemove',
  73. 'chart.tooltips.hotspot': 3,
  74. 'chart.tooltips.css.class': 'RGraph_tooltip',
  75. 'chart.tooltips.highlight': true,
  76. 'chart.tooltips.coords.page': false,
  77. 'chart.units.pre': '',
  78. 'chart.units.post': '',
  79. 'chart.numyticks': 10,
  80. 'chart.tickmarks': 'cross',
  81. 'chart.ticksize': 5,
  82. 'chart.numxticks': true,
  83. 'chart.xaxis': true,
  84. 'chart.gutter.left': 25,
  85. 'chart.gutter.right': 25,
  86. 'chart.gutter.top': 25,
  87. 'chart.gutter.bottom': 25,
  88. 'chart.xmin': 0,
  89. 'chart.xmax': 0,
  90. 'chart.ymax': null,
  91. 'chart.ymin': 0,
  92. 'chart.scale.decimals': null,
  93. 'chart.scale.point': '.',
  94. 'chart.scale.thousand': ',',
  95. 'chart.title': '',
  96. 'chart.title.background': null,
  97. 'chart.title.hpos': null,
  98. 'chart.title.vpos': null,
  99. 'chart.title.bold': true,
  100. 'chart.title.font': null,
  101. 'chart.title.xaxis': '',
  102. 'chart.title.xaxis.bold': true,
  103. 'chart.title.xaxis.size': null,
  104. 'chart.title.xaxis.font': null,
  105. 'chart.title.yaxis': '',
  106. 'chart.title.yaxis.bold': true,
  107. 'chart.title.yaxis.size': null,
  108. 'chart.title.yaxis.font': null,
  109. 'chart.title.yaxis.color': null,
  110. 'chart.title.xaxis.pos': null,
  111. 'chart.title.yaxis.pos': null,
  112. 'chart.title.yaxis.x': null,
  113. 'chart.title.yaxis.y': null,
  114. 'chart.title.xaxis.x': null,
  115. 'chart.title.xaxis.y': null,
  116. 'chart.title.x': null,
  117. 'chart.title.y': null,
  118. 'chart.title.halign': null,
  119. 'chart.title.valign': null,
  120. 'chart.labels': [],
  121. 'chart.labels.ingraph': null,
  122. 'chart.labels.above': false,
  123. 'chart.labels.above.size': 8,
  124. 'chart.labels.above.decimals': 0,
  125. 'chart.ylabels': true,
  126. 'chart.ylabels.count': 5,
  127. 'chart.ylabels.invert': false,
  128. 'chart.ylabels.specific': null,
  129. 'chart.ylabels.inside': false,
  130. 'chart.contextmenu': null,
  131. 'chart.defaultcolor': 'black',
  132. 'chart.xaxispos': 'bottom',
  133. 'chart.yaxispos': 'left',
  134. 'chart.crosshairs': false,
  135. 'chart.crosshairs.color': '#333',
  136. 'chart.crosshairs.linewidth': 1,
  137. 'chart.crosshairs.coords': false,
  138. 'chart.crosshairs.coords.fixed':true,
  139. 'chart.crosshairs.coords.fadeout':false,
  140. 'chart.crosshairs.coords.labels.x': 'X',
  141. 'chart.crosshairs.coords.labels.y': 'Y',
  142. 'chart.crosshairs.hline': true,
  143. 'chart.crosshairs.vline': true,
  144. 'chart.annotatable': false,
  145. 'chart.annotate.color': 'black',
  146. 'chart.line': false,
  147. 'chart.line.linewidth': 1,
  148. 'chart.line.colors': ['green', 'red'],
  149. 'chart.line.shadow.color': 'rgba(0,0,0,0)',
  150. 'chart.line.shadow.blur': 2,
  151. 'chart.line.shadow.offsetx': 3,
  152. 'chart.line.shadow.offsety': 3,
  153. 'chart.line.stepped': false,
  154. 'chart.line.visible': true,
  155. 'chart.noaxes': false,
  156. 'chart.noyaxis': false,
  157. 'chart.key': null,
  158. 'chart.key.background': 'white',
  159. 'chart.key.position': 'graph',
  160. 'chart.key.halign': 'right',
  161. 'chart.key.shadow': false,
  162. 'chart.key.shadow.color': '#666',
  163. 'chart.key.shadow.blur': 3,
  164. 'chart.key.shadow.offsetx': 2,
  165. 'chart.key.shadow.offsety': 2,
  166. 'chart.key.position.gutter.boxed': false,
  167. 'chart.key.position.x': null,
  168. 'chart.key.position.y': null,
  169. 'chart.key.interactive': false,
  170. 'chart.key.interactive.highlight.chart.fill': 'rgba(255,0,0,0.9)',
  171. 'chart.key.interactive.highlight.label': 'rgba(255,0,0,0.2)',
  172. 'chart.key.color.shape': 'square',
  173. 'chart.key.rounded': true,
  174. 'chart.key.linewidth': 1,
  175. 'chart.key.colors': null,
  176. 'chart.key.text.color': 'black',
  177. 'chart.axis.color': 'black',
  178. 'chart.zoom.factor': 1.5,
  179. 'chart.zoom.fade.in': true,
  180. 'chart.zoom.fade.out': true,
  181. 'chart.zoom.hdir': 'right',
  182. 'chart.zoom.vdir': 'down',
  183. 'chart.zoom.frames': 25,
  184. 'chart.zoom.delay': 16.666,
  185. 'chart.zoom.shadow': true,
  186. 'chart.zoom.background': true,
  187. 'chart.zoom.action': 'zoom',
  188. 'chart.boxplot.width': 1,
  189. 'chart.boxplot.capped': true,
  190. 'chart.resizable': false,
  191. 'chart.resize.handle.background': null,
  192. 'chart.xmin': 0,
  193. 'chart.labels.specific.align': 'left',
  194. 'chart.xscale': false,
  195. 'chart.xscale.units.pre': '',
  196. 'chart.xscale.units.post': '',
  197. 'chart.xscale.numlabels': 10,
  198. 'chart.xscale.formatter': null,
  199. 'chart.xscale.decimals': 0,
  200. 'chart.xscale.thousand': ',',
  201. 'chart.xscale.point': '.',
  202. 'chart.noendxtick': false,
  203. 'chart.noendytick': true,
  204. 'chart.events.mousemove': null,
  205. 'chart.events.click': null,
  206. 'chart.highlight.stroke': 'rgba(0,0,0,0)',
  207. 'chart.highlight.fill': 'rgba(255,255,255,0.7)'
  208. }
  209. // Handle multiple datasets being given as one argument
  210. if (arguments[1][0] && arguments[1][0][0] && typeof(arguments[1][0][0]) == 'object') {
  211. // Store the data set(s)
  212. for (var i=0; i<arguments[1].length; ++i) {
  213. this.data[i] = arguments[1][i];
  214. }
  215. // Handle multiple data sets being supplied as seperate arguments
  216. } else {
  217. // Store the data set(s)
  218. for (var i=1; i<arguments.length; ++i) {
  219. this.data[i - 1] = arguments[i];
  220. }
  221. }
  222. /**
  223. * This allows the data points to be given as dates as well as numbers. Formats supported by RGraph.parseDate() are accepted.
  224. */
  225. for (var i=0; i<this.data.length; ++i) {
  226. for (var j=0; j<this.data[i].length; ++j) {
  227. if (typeof(this.data[i][j][0]) == 'string') {
  228. this.data[i][j][0] = RGraph.parseDate(this.data[i][j][0]);
  229. }
  230. }
  231. }
  232. /**
  233. * Now make the data_arr array - all the data as one big array
  234. */
  235. this.data_arr = [];
  236. for (var i=0; i<this.data.length; ++i) {
  237. for (var j=0; j<this.data[i].length; ++j) {
  238. this.data_arr.push(this.data[i][j]);
  239. }
  240. }
  241. // Create the $ objects so that they can be used
  242. for (var i=0; i<this.data_arr.length; ++i) {
  243. this['$' + i] = {}
  244. }
  245. // Check for support
  246. if (!this.canvas) {
  247. alert('[SCATTER] No canvas support');
  248. return;
  249. }
  250. /**
  251. * Translate half a pixel for antialiasing purposes - but only if it hasn't beeen
  252. * done already
  253. */
  254. if (!this.canvas.__rgraph_aa_translated__) {
  255. this.context.translate(0.5,0.5);
  256. this.canvas.__rgraph_aa_translated__ = true;
  257. }
  258. // Short variable names
  259. var RG = RGraph;
  260. var ca = this.canvas;
  261. var co = ca.getContext('2d');
  262. var prop = this.properties;
  263. var jq = jQuery;
  264. var pa = RG.Path;
  265. var win = window;
  266. var doc = document;
  267. var ma = Math;
  268. /**
  269. * "Decorate" the object with the generic effects if the effects library has been included
  270. */
  271. if (RG.Effects && typeof RG.Effects.decorate === 'function') {
  272. RG.Effects.decorate(this);
  273. }
  274. /**
  275. * A simple setter
  276. *
  277. * @param string name The name of the property to set
  278. * @param string value The value of the property
  279. */
  280. this.set =
  281. this.Set = function (name, value)
  282. {
  283. /**
  284. * This should be done first - prepend the propertyy name with "chart." if necessary
  285. */
  286. if (name.substr(0,6) != 'chart.') {
  287. name = 'chart.' + name;
  288. }
  289. /**
  290. * BC for chart.xticks
  291. */
  292. if (name == 'chart.xticks') {
  293. name == 'chart.numxticks';
  294. }
  295. /**
  296. * This is here because the key expects a name of "chart.colors"
  297. */
  298. if (name == 'chart.line.colors') {
  299. prop['chart.colors'] = value;
  300. }
  301. /**
  302. * Allow compatibility with older property names
  303. */
  304. if (name == 'chart.tooltip.hotspot') {
  305. name = 'chart.tooltips.hotspot';
  306. }
  307. /**
  308. * chart.yaxispos should be left or right
  309. */
  310. if (name == 'chart.yaxispos' && value != 'left' && value != 'right') {
  311. alert("[SCATTER] chart.yaxispos should be left or right. You've set it to: '" + value + "' Changing it to left");
  312. value = 'left';
  313. }
  314. /**
  315. * Check for xaxispos
  316. */
  317. if (name == 'chart.xaxispos' ) {
  318. if (value != 'bottom' && value != 'center') {
  319. alert('[SCATTER] (' + this.id + ') chart.xaxispos should be center or bottom. Tried to set it to: ' + value + ' Changing it to center');
  320. value = 'center';
  321. }
  322. }
  323. prop[name.toLowerCase()] = value;
  324. return this;
  325. };
  326. /**
  327. * A simple getter
  328. *
  329. * @param string name The name of the property to set
  330. */
  331. this.get =
  332. this.Get = function (name)
  333. {
  334. /**
  335. * This should be done first - prepend the property name with "chart." if necessary
  336. */
  337. if (name.substr(0,6) != 'chart.') {
  338. name = 'chart.' + name;
  339. }
  340. return prop[name];
  341. };
  342. /**
  343. * The function you call to draw the line chart
  344. */
  345. this.draw =
  346. this.Draw = function ()
  347. {
  348. // MUST be the first thing done!
  349. if (typeof prop['chart.background.image'] === 'string') {
  350. RG.DrawBackgroundImage(this);
  351. }
  352. /**
  353. * Fire the onbeforedraw event
  354. */
  355. RG.FireCustomEvent(this, 'onbeforedraw');
  356. /**
  357. * Parse the colors. This allows for simple gradient syntax
  358. */
  359. if (!this.colorsParsed) {
  360. this.parseColors();
  361. // Don't want to do this again
  362. this.colorsParsed = true;
  363. }
  364. /**
  365. * Stop this growing uncontrollably
  366. */
  367. this.coordsText = [];
  368. /**
  369. * This is new in May 2011 and facilitates indiviual gutter settings,
  370. * eg chart.gutter.left
  371. */
  372. this.gutterLeft = prop['chart.gutter.left'];
  373. this.gutterRight = prop['chart.gutter.right'];
  374. this.gutterTop = prop['chart.gutter.top'];
  375. this.gutterBottom = prop['chart.gutter.bottom'];
  376. // Go through all the data points and see if a tooltip has been given
  377. this.hasTooltips = false;
  378. var overHotspot = false;
  379. // Reset the coords array
  380. this.coords = [];
  381. /**
  382. * This facilitates the xmax, xmin and X values being dates
  383. */
  384. if (typeof(prop['chart.xmin']) == 'string') prop['chart.xmin'] = RG.parseDate(prop['chart.xmin']);
  385. if (typeof(prop['chart.xmax']) == 'string') prop['chart.xmax'] = RG.parseDate(prop['chart.xmax']);
  386. /**
  387. * Look for tooltips and populate chart.tooltips
  388. *
  389. * NB 26/01/2011 Updated so that chart.tooltips is ALWAYS populated
  390. */
  391. if (!RGraph.ISOLD) {
  392. this.Set('chart.tooltips', []);
  393. for (var i=0,len=this.data.length; i<len; i+=1) {
  394. for (var j =0,len2=this.data[i].length;j<len2; j+=1) {
  395. if (this.data[i][j] && this.data[i][j][3]) {
  396. prop['chart.tooltips'].push(this.data[i][j][3]);
  397. this.hasTooltips = true;
  398. } else {
  399. prop['chart.tooltips'].push(null);
  400. }
  401. }
  402. }
  403. }
  404. // Reset the maximum value
  405. this.max = 0;
  406. // Work out the maximum Y value
  407. //if (prop['chart.ymax'] && prop['chart.ymax'] > 0) {
  408. if (typeof prop['chart.ymax'] === 'number') {
  409. this.max = prop['chart.ymax'];
  410. this.min = prop['chart.ymin'] ? prop['chart.ymin'] : 0;
  411. this.scale2 = RG.getScale2(this, {
  412. 'max':this.max,
  413. 'min':this.min,
  414. 'strict':true,
  415. 'scale.thousand':prop['chart.scale.thousand'],
  416. 'scale.point':prop['chart.scale.point'],
  417. 'scale.decimals':prop['chart.scale.decimals'],
  418. 'ylabels.count':prop['chart.ylabels.count'],
  419. 'scale.round':prop['chart.scale.round'],
  420. 'units.pre': prop['chart.units.pre'],
  421. 'units.post': prop['chart.units.post']
  422. });
  423. this.max = this.scale2.max;
  424. this.min = this.scale2.min;
  425. var decimals = prop['chart.scale.decimals'];
  426. } else {
  427. var i = 0;
  428. var j = 0;
  429. for (i=0,len=this.data.length; i<len; i+=1) {
  430. for (j=0,len2=this.data[i].length; j<len2; j+=1) {
  431. if (this.data[i][j][1] != null) {
  432. this.max = Math.max(this.max, typeof(this.data[i][j][1]) == 'object' ? RG.array_max(this.data[i][j][1]) : Math.abs(this.data[i][j][1]));
  433. }
  434. }
  435. }
  436. this.min = prop['chart.ymin'] ? prop['chart.ymin'] : 0;
  437. this.scale2 = RG.getScale2(this, {
  438. 'max':this.max,
  439. 'min':this.min,
  440. 'scale.thousand':prop['chart.scale.thousand'],
  441. 'scale.point':prop['chart.scale.point'],
  442. 'scale.decimals':prop['chart.scale.decimals'],
  443. 'ylabels.count':prop['chart.ylabels.count'],
  444. 'scale.round':prop['chart.scale.round'],
  445. 'units.pre': prop['chart.units.pre'],
  446. 'units.post': prop['chart.units.post']
  447. });
  448. this.max = this.scale2.max;
  449. this.min = this.scale2.min;
  450. }
  451. this.grapharea = ca.height - this.gutterTop - this.gutterBottom;
  452. // Progressively Draw the chart
  453. RG.background.Draw(this);
  454. /**
  455. * Draw any horizontal bars that have been specified
  456. */
  457. if (prop['chart.background.hbars'] && prop['chart.background.hbars'].length) {
  458. RG.DrawBars(this);
  459. }
  460. /**
  461. * Draw any vertical bars that have been specified
  462. */
  463. if (prop['chart.background.vbars'] && prop['chart.background.vbars'].length) {
  464. this.DrawVBars();
  465. }
  466. if (!prop['chart.noaxes']) {
  467. this.DrawAxes();
  468. }
  469. this.DrawLabels();
  470. i = 0;
  471. for(i=0; i<this.data.length; ++i) {
  472. this.DrawMarks(i);
  473. // Set the shadow
  474. co.shadowColor = prop['chart.line.shadow.color'];
  475. co.shadowOffsetX = prop['chart.line.shadow.offsetx'];
  476. co.shadowOffsetY = prop['chart.line.shadow.offsety'];
  477. co.shadowBlur = prop['chart.line.shadow.blur'];
  478. this.DrawLine(i);
  479. // Turn the shadow off
  480. RG.NoShadow(this);
  481. }
  482. if (prop['chart.line']) {
  483. for (var i=0,len=this.data.length;i<len; i+=1) {
  484. this.DrawMarks(i); // Call this again so the tickmarks appear over the line
  485. }
  486. }
  487. /**
  488. * Setup the context menu if required
  489. */
  490. if (prop['chart.contextmenu']) {
  491. RG.ShowContext(this);
  492. }
  493. /**
  494. * Draw the key if necessary
  495. */
  496. if (prop['chart.key'] && prop['chart.key'].length) {
  497. RG.DrawKey(this, prop['chart.key'], prop['chart.line.colors']);
  498. }
  499. /**
  500. * Draw " above" labels if enabled
  501. */
  502. if (prop['chart.labels.above']) {
  503. this.DrawAboveLabels();
  504. }
  505. /**
  506. * Draw the "in graph" labels, using the member function, NOT the shared function in RGraph.common.core.js
  507. */
  508. this.DrawInGraphLabels(this);
  509. /**
  510. * This function enables resizing
  511. */
  512. if (prop['chart.resizable']) {
  513. RG.AllowResizing(this);
  514. }
  515. /**
  516. * This installs the event listeners
  517. */
  518. RG.InstallEventListeners(this);
  519. /**
  520. * Fire the onfirstdraw event
  521. */
  522. if (this.firstDraw) {
  523. RG.fireCustomEvent(this, 'onfirstdraw');
  524. this.firstDrawFunc();
  525. this.firstDraw = false;
  526. }
  527. /**
  528. * Fire the RGraph ondraw event
  529. */
  530. RG.FireCustomEvent(this, 'ondraw');
  531. return this;
  532. }
  533. /**
  534. * Draws the axes of the scatter graph
  535. */
  536. this.drawAxes =
  537. this.DrawAxes = function ()
  538. {
  539. var graphHeight = ca.height - this.gutterTop - this.gutterBottom;
  540. co.beginPath();
  541. co.strokeStyle = prop['chart.axis.color'];
  542. co.lineWidth = (prop['chart.axis.linewidth'] || 1) + 0.001; // Strange Chrome bug
  543. // Draw the Y axis
  544. if (prop['chart.noyaxis'] == false) {
  545. if (prop['chart.yaxispos'] == 'left') {
  546. co.moveTo(this.gutterLeft, this.gutterTop);
  547. co.lineTo(this.gutterLeft, ca.height - this.gutterBottom);
  548. } else {
  549. co.moveTo(ca.width - this.gutterRight, this.gutterTop);
  550. co.lineTo(ca.width - this.gutterRight, ca.height - this.gutterBottom);
  551. }
  552. }
  553. // Draw the X axis
  554. if (prop['chart.xaxis']) {
  555. if (prop['chart.xaxispos'] == 'center') {
  556. co.moveTo(this.gutterLeft, Math.round(this.gutterTop + ((ca.height - this.gutterTop - this.gutterBottom) / 2)));
  557. co.lineTo(ca.width - this.gutterRight, Math.round(this.gutterTop + ((ca.height - this.gutterTop - this.gutterBottom) / 2)));
  558. } else {
  559. co.moveTo(this.gutterLeft, ca.height - this.gutterBottom);
  560. co.lineTo(ca.width - this.gutterRight, ca.height - this.gutterBottom);
  561. }
  562. }
  563. // Draw the Y tickmarks
  564. if (prop['chart.noyaxis'] == false) {
  565. var numyticks = prop['chart.numyticks'];
  566. //for (y=this.gutterTop; y < ca.height - this.gutterBottom + (prop['chart.xaxispos'] == 'center' ? 1 : 0) ; y+=(graphHeight / numyticks)) {
  567. for (i=0; i<numyticks; ++i) {
  568. var y = ((ca.height - this.gutterTop - this.gutterBottom) / numyticks) * i;
  569. y = y + this.gutterTop;
  570. if (prop['chart.xaxispos'] == 'center' && i == (numyticks / 2)) {
  571. continue;
  572. }
  573. if (prop['chart.yaxispos'] == 'left') {
  574. co.moveTo(this.gutterLeft, Math.round(y));
  575. co.lineTo(this.gutterLeft - 3, Math.round(y));
  576. } else {
  577. co.moveTo(ca.width - this.gutterRight +3, Math.round(y));
  578. co.lineTo(ca.width - this.gutterRight, Math.round(y));
  579. }
  580. }
  581. /**
  582. * Draw the end Y tickmark if the X axis is in the centre
  583. */
  584. if (prop['chart.numyticks'] > 0) {
  585. if (prop['chart.xaxispos'] == 'center' && prop['chart.yaxispos'] == 'left') {
  586. co.moveTo(this.gutterLeft, Math.round(ca.height - this.gutterBottom));
  587. co.lineTo(this.gutterLeft - 3, Math.round(ca.height - this.gutterBottom));
  588. } else if (prop['chart.xaxispos'] == 'center') {
  589. co.moveTo(ca.width - this.gutterRight + 3, Math.round(ca.height - this.gutterBottom));
  590. co.lineTo(ca.width - this.gutterRight, Math.round(ca.height - this.gutterBottom));
  591. }
  592. }
  593. /**
  594. * Draw an extra tick if the X axis isn't being shown
  595. */
  596. if (prop['chart.xaxis'] == false && prop['chart.yaxispos'] == 'left') {
  597. co.moveTo(this.gutterLeft, Math.round(ca.height - this.gutterBottom));
  598. co.lineTo(this.gutterLeft - 3, Math.round(ca.height - this.gutterBottom));
  599. } else if (prop['chart.xaxis'] == false && prop['chart.yaxispos'] == 'right') {
  600. co.moveTo(ca.width - this.gutterRight, Math.round(ca.height - this.gutterBottom));
  601. co.lineTo(ca.width - this.gutterRight + 3, Math.round(ca.height - this.gutterBottom));
  602. }
  603. }
  604. /**
  605. * Draw the X tickmarks
  606. */
  607. if (prop['chart.numxticks'] > 0 && prop['chart.xaxis']) {
  608. var x = 0;
  609. var y = (prop['chart.xaxispos'] == 'center') ? this.gutterTop + (this.grapharea / 2) : (ca.height - this.gutterBottom);
  610. this.xTickGap = (prop['chart.labels'] && prop['chart.labels'].length) ? ((ca.width - this.gutterLeft - this.gutterRight ) / prop['chart.labels'].length) : (ca.width - this.gutterLeft - this.gutterRight) / 10;
  611. /**
  612. * This allows the number of X tickmarks to be specified
  613. */
  614. if (typeof(prop['chart.numxticks']) == 'number') {
  615. this.xTickGap = (ca.width - this.gutterLeft - this.gutterRight) / prop['chart.numxticks'];
  616. }
  617. for (x=(this.gutterLeft + (prop['chart.yaxispos'] == 'left' && prop['chart.noyaxis'] == false ? this.xTickGap : 0) );
  618. x <= (ca.width - this.gutterRight - (prop['chart.yaxispos'] == 'left' || prop['chart.noyaxis'] == true ? -1 : 1));
  619. x += this.xTickGap) {
  620. if (prop['chart.yaxispos'] == 'left' && prop['chart.noendxtick'] == true && x == (ca.width - this.gutterRight) ) {
  621. continue;
  622. } else if (prop['chart.yaxispos'] == 'right' && prop['chart.noendxtick'] == true && x == this.gutterLeft) {
  623. continue;
  624. }
  625. co.moveTo(Math.round(x), y - (prop['chart.xaxispos'] == 'center' ? 3 : 0));
  626. co.lineTo(Math.round(x), y + 3);
  627. }
  628. }
  629. co.stroke();
  630. /**
  631. * Reset the linewidth back to one
  632. */
  633. co.lineWidth = 1;
  634. };
  635. /**
  636. * Draws the labels on the scatter graph
  637. */
  638. this.drawLabels =
  639. this.DrawLabels = function ()
  640. {
  641. co.fillStyle = prop['chart.text.color'];
  642. var font = prop['chart.text.font'];
  643. var xMin = prop['chart.xmin'];
  644. var xMax = prop['chart.xmax'];
  645. var yMax = this.scale2.max;
  646. var yMin = prop['chart.ymin'] ? prop['chart.ymin'] : 0;
  647. var text_size = prop['chart.text.size'];
  648. var units_pre = prop['chart.units.pre'];
  649. var units_post = prop['chart.units.post'];
  650. var numYLabels = prop['chart.ylabels.count'];
  651. var invert = prop['chart.ylabels.invert'];
  652. var inside = prop['chart.ylabels.inside'];
  653. var context = co;
  654. var canvas = ca;
  655. var boxed = false;
  656. this.halfTextHeight = text_size / 2;
  657. this.halfGraphHeight = (ca.height - this.gutterTop - this.gutterBottom) / 2;
  658. /**
  659. * Draw the Y yaxis labels, be it at the top or center
  660. */
  661. if (prop['chart.ylabels']) {
  662. var xPos = prop['chart.yaxispos'] == 'left' ? this.gutterLeft - 5 : ca.width - this.gutterRight + 5;
  663. var align = prop['chart.yaxispos'] == 'right' ? 'left' : 'right';
  664. /**
  665. * Now change the two things above if chart.ylabels.inside is specified
  666. */
  667. if (inside) {
  668. if (prop['chart.yaxispos'] == 'left') {
  669. xPos = prop['chart.gutter.left'] + 5;
  670. align = 'left';
  671. boxed = true;
  672. } else {
  673. xPos = ca.width - prop['chart.gutter.right'] - 5;
  674. align = 'right';
  675. boxed = true;
  676. }
  677. }
  678. if (prop['chart.xaxispos'] == 'center') {
  679. /**
  680. * Specific Y labels
  681. */
  682. if (typeof(prop['chart.ylabels.specific']) == 'object' && prop['chart.ylabels.specific'] != null && prop['chart.ylabels.specific'].length) {
  683. var labels = prop['chart.ylabels.specific'];
  684. if (prop['chart.ymin'] > 0) {
  685. labels = [];
  686. for (var i=0; i<(prop['chart.ylabels.specific'].length - 1); ++i) {
  687. labels.push(prop['chart.ylabels.specific'][i]);
  688. }
  689. }
  690. for (var i=0; i<labels.length; ++i) {
  691. var y = this.gutterTop + (i * (this.grapharea / (labels.length * 2) ) );
  692. RG.Text2(this, {'font':font,
  693. 'size':text_size,
  694. 'x':xPos,
  695. 'y':y,
  696. 'text':labels[i],
  697. 'valign':'center',
  698. 'halign':align,
  699. 'bounding':boxed,
  700. 'tag': 'labels.specific'
  701. });
  702. }
  703. var reversed_labels = RG.array_reverse(labels);
  704. for (var i=0; i<reversed_labels.length; ++i) {
  705. var y = this.gutterTop + (this.grapharea / 2) + ((i+1) * (this.grapharea / (labels.length * 2) ) );
  706. RG.Text2(this, {'font':font,
  707. 'size':text_size,
  708. 'x':xPos,
  709. 'y':y,
  710. 'text':reversed_labels[i],
  711. 'valign':'center',
  712. 'halign':align,
  713. 'bounding':boxed,
  714. 'tag': 'labels.specific'
  715. });
  716. }
  717. /**
  718. * Draw the center label if chart.ymin is specified
  719. */
  720. if (prop['chart.ymin'] != 0) {
  721. RG.Text2(this, {'font':font,
  722. 'size':text_size,
  723. 'x':xPos,
  724. 'y':(this.grapharea / 2) + this.gutterTop,
  725. 'text':prop['chart.ylabels.specific'][prop['chart.ylabels.specific'].length - 1],
  726. 'valign':'center',
  727. 'halign':align,
  728. 'bounding':boxed,
  729. 'tag': 'labels.specific'
  730. });
  731. }
  732. }
  733. if (!prop['chart.ylabels.specific'] && typeof numYLabels == 'number') {
  734. /**
  735. * Draw the top half
  736. */
  737. for (var i=0,len=this.scale2.labels.length; i<len; i+=1) {
  738. //var value = ((this.max - this.min)/ numYLabels) * (i+1);
  739. //value = (invert ? this.max - value : value);
  740. //if (!invert) value += this.min;
  741. //value = value.toFixed(prop['chart.scale.decimals']);
  742. if (!invert) {
  743. RG.Text2(this, {'font':font,
  744. 'size': text_size,
  745. 'x': xPos,
  746. 'y': this.gutterTop + this.halfGraphHeight - (((i + 1)/numYLabels) * this.halfGraphHeight),
  747. 'valign': 'center',
  748. 'halign':align,
  749. 'bounding': boxed,
  750. 'boundingFill': 'white',
  751. 'text': this.scale2.labels[i],
  752. 'tag': 'scale'
  753. });
  754. } else {
  755. RG.Text2(this, {'font':font,
  756. 'size': text_size,
  757. 'x': xPos,
  758. 'y': this.gutterTop + this.halfGraphHeight - ((i/numYLabels) * this.halfGraphHeight),
  759. 'valign': 'center',
  760. 'halign':align,
  761. 'bounding': boxed,
  762. 'boundingFill': 'white',
  763. 'text': this.scale2.labels[this.scale2.labels.length - (i + 1)],
  764. 'tag': 'scale'
  765. });
  766. }
  767. }
  768. /**
  769. * Draw the bottom half
  770. */
  771. for (var i=0,len=this.scale2.labels.length; i<len; i+=1) {
  772. //var value = (((this.max - this.min)/ numYLabels) * i) + this.min;
  773. // value = (invert ? value : this.max - (value - this.min)).toFixed(prop['chart.scale.decimals']);
  774. if (!invert) {
  775. RG.Text2(this, {'font':font,
  776. 'size': text_size,
  777. 'x': xPos,
  778. 'y': this.gutterTop + this.halfGraphHeight + this.halfGraphHeight - ((i/numYLabels) * this.halfGraphHeight),
  779. 'valign': 'center',
  780. 'halign':align,
  781. 'bounding': boxed,
  782. 'boundingFill': 'white',
  783. 'text': '-' + this.scale2.labels[len - (i+1)],
  784. 'tag': 'scale'
  785. });
  786. } else {
  787. // This ensures that the center label isn't drawn twice
  788. if (i == (len - 1)&& invert) {
  789. continue;
  790. }
  791. RG.Text2(this, {'font':font,
  792. 'size': text_size,
  793. 'x': xPos,
  794. 'y': this.gutterTop + this.halfGraphHeight + this.halfGraphHeight - (((i + 1)/numYLabels) * this.halfGraphHeight),
  795. 'valign': 'center',
  796. 'halign':align,
  797. 'bounding': boxed,
  798. 'boundingFill': 'white',
  799. 'text': '-' + this.scale2.labels[i],
  800. 'tag': 'scale'
  801. });
  802. }
  803. }
  804. // If ymin is specified draw that
  805. if (!invert && yMin > 0) {
  806. RG.Text2(this, {'font':font,
  807. 'size': text_size,
  808. 'x': xPos,
  809. 'y': this.gutterTop + this.halfGraphHeight,
  810. 'valign': 'center',
  811. 'halign':align,
  812. 'bounding': boxed,
  813. 'boundingFill': 'white',
  814. 'text': RG.number_format(this, yMin.toFixed(prop['chart.scale.decimals']), units_pre, units_post),
  815. 'tag': 'scale'
  816. });
  817. }
  818. if (invert) {
  819. RG.Text2(this, {'font':font,
  820. 'size': text_size,
  821. 'x': xPos,
  822. 'y': this.gutterTop,
  823. 'valign': 'center',
  824. 'halign':align,
  825. 'bounding': boxed,
  826. 'boundingFill': 'white',
  827. 'text': RG.number_format(this, yMin.toFixed(prop['chart.scale.decimals']), units_pre, units_post),
  828. 'tag': 'scale'
  829. });
  830. RG.Text2(this, {'font':font,
  831. 'size': text_size,
  832. 'x': xPos,
  833. 'y': this.gutterTop + (this.halfGraphHeight * 2),
  834. 'valign': 'center',
  835. 'halign':align,
  836. 'bounding': boxed,
  837. 'boundingFill': 'white',
  838. 'text': '-' + RG.number_format(this, yMin.toFixed(prop['chart.scale.decimals']), units_pre, units_post),
  839. 'tag': 'scale'
  840. });
  841. }
  842. }
  843. // X axis at the bottom
  844. } else {
  845. var xPos = prop['chart.yaxispos'] == 'left' ? this.gutterLeft - 5 : ca.width - this.gutterRight + 5;
  846. var align = prop['chart.yaxispos'] == 'right' ? 'left' : 'right';
  847. if (inside) {
  848. if (prop['chart.yaxispos'] == 'left') {
  849. xPos = prop['chart.gutter.left'] + 5;
  850. align = 'left';
  851. boxed = true;
  852. } else {
  853. xPos = ca.width - obj.gutterRight - 5;
  854. align = 'right';
  855. boxed = true;
  856. }
  857. }
  858. /**
  859. * Specific Y labels
  860. */
  861. if (typeof prop['chart.ylabels.specific'] == 'object' && prop['chart.ylabels.specific']) {
  862. var labels = prop['chart.ylabels.specific'];
  863. // Lose the last label
  864. if (prop['chart.ymin'] > 9999) {
  865. labels = [];
  866. for (var i=0; i<(prop['chart.ylabels.specific'].length - 1); ++i) {
  867. labels.push(prop['chart.ylabels.specific'][i]);
  868. }
  869. }
  870. for (var i=0,len=labels.length; i<len; i+=1) {
  871. var y = this.gutterTop + (i * (this.grapharea / (len - 1)) );
  872. RG.Text2(this, {'font':font,
  873. 'size':text_size,
  874. 'x':xPos,
  875. 'y':y,
  876. 'text':labels[i],
  877. 'halign':align,
  878. 'valign':'center',
  879. 'bounding':boxed,
  880. 'tag': 'scale'
  881. });
  882. }
  883. /**
  884. * X axis at the bottom
  885. */
  886. } else {
  887. if (typeof(numYLabels) == 'number') {
  888. if (invert) {
  889. for (var i=0; i<numYLabels; ++i) {
  890. //var value = ((this.max - this.min)/ numYLabels) * i;
  891. // value = value.toFixed(prop['chart.scale.decimals']);
  892. var interval = (ca.height - this.gutterTop - this.gutterBottom) / numYLabels;
  893. RG.Text2(this, {'font':font,
  894. 'size': text_size,
  895. 'x': xPos,
  896. 'y': this.gutterTop + ((i+1) * interval),
  897. 'valign': 'center',
  898. 'halign':align,
  899. 'bounding': boxed,
  900. 'boundingFill': 'white',
  901. 'text': this.scale2.labels[i],
  902. 'tag': 'scale'
  903. });
  904. }
  905. // No X axis being shown and there's no ymin. If ymin IS set its added further down
  906. if (!prop['chart.xaxis'] && !prop['chart.ymin']) {
  907. RG.Text2(this, {'font':font,
  908. 'size': text_size,
  909. 'x': xPos,
  910. 'y': this.gutterTop,
  911. 'valign': 'center',
  912. 'halign':align,
  913. 'bounding': boxed,
  914. 'boundingFill': 'white',
  915. 'text': RG.number_format(this, (this.min).toFixed(prop['chart.scale.decimals']), units_pre, units_post),
  916. 'tag': 'scale'
  917. });
  918. }
  919. } else {
  920. for (var i=0,len=this.scale2.labels.length; i<len; i+=1) {
  921. //var value = ((this.max - this.min)/ numYLabels) * (i+1);
  922. // value = (invert ? this.max - value : value);
  923. // if (!invert) value += this.min;
  924. // value = value.toFixed(prop['chart.scale.decimals']);
  925. RG.Text2(this, {'font':font,
  926. 'size': text_size,
  927. 'x': xPos,
  928. 'y': this.gutterTop + this.grapharea - (((i + 1)/this.scale2.labels.length) * this.grapharea),
  929. 'valign': 'center',
  930. 'halign':align,
  931. 'bounding': boxed,
  932. 'boundingFill': 'white',
  933. 'text': this.scale2.labels[i],
  934. 'tag': 'scale'
  935. });
  936. }
  937. if (!prop['chart.xaxis'] && prop['chart.ymin'] == 0) {
  938. RG.Text2(this, {'font':font,
  939. 'size': text_size,
  940. 'x': xPos,
  941. 'y': ca.height - this.gutterBottom,
  942. 'valign': 'center',
  943. 'halign':align,
  944. 'bounding': boxed,
  945. 'boundingFill': 'white',
  946. 'text': RG.number_format(this, (0).toFixed(prop['chart.scale.decimals']), units_pre, units_post),
  947. 'tag': 'scale'
  948. });
  949. }
  950. }
  951. }
  952. if (prop['chart.ymin'] && !invert) {
  953. RG.Text2(this, {'font':font,
  954. 'size': text_size,
  955. 'x': xPos,
  956. 'y': ca.height - this.gutterBottom,
  957. 'valign': 'center',
  958. 'halign':align,
  959. 'bounding': boxed,
  960. 'boundingFill': 'white',
  961. 'text': RG.number_format(this, prop['chart.ymin'].toFixed(prop['chart.scale.decimals']), units_pre, units_post),
  962. 'tag': 'scale'
  963. });
  964. } else if (invert) {
  965. RG.Text2(this, {'font':font,
  966. 'size': text_size,
  967. 'x': xPos,
  968. 'y': this.gutterTop,
  969. 'valign': 'center',
  970. 'halign':align,
  971. 'bounding': boxed,
  972. 'boundingFill': 'white',
  973. 'text': RG.number_format(this, prop['chart.ymin'].toFixed(prop['chart.scale.decimals']), units_pre, units_post),
  974. 'tag': 'scale'
  975. });
  976. }
  977. }
  978. }
  979. }
  980. /**
  981. * Draw an X scale
  982. */
  983. if (prop['chart.xscale']) {
  984. var numXLabels = prop['chart.xscale.numlabels'];
  985. var y = ca.height - this.gutterBottom + 5 + (text_size / 2);
  986. var units_pre_x = prop['chart.xscale.units.pre'];
  987. var units_post_x = prop['chart.xscale.units.post'];
  988. var decimals = prop['chart.xscale.decimals'];
  989. var point = prop['chart.xscale.point'];
  990. var thousand = prop['chart.xscale.thousand'];
  991. if (!prop['chart.xmax']) {
  992. var xmax = 0;
  993. var xmin = prop['chart.xmin'];
  994. for (var ds=0,len=this.data.length; ds<len; ds+=1) {
  995. for (var point=0,len2=this.data[ds].length; point<len2; point+=1) {
  996. xmax = Math.max(xmax, this.data[ds][point][0]);
  997. }
  998. }
  999. } else {
  1000. xmax = prop['chart.xmax'];
  1001. xmin = prop['chart.xmin']
  1002. }
  1003. this.xscale2 = RG.getScale2(this, {'max':xmax,
  1004. 'min': xmin,
  1005. 'scale.decimals': decimals,
  1006. 'scale.point': point,
  1007. 'scale.thousand': thousand,
  1008. 'units.pre': units_pre_x,
  1009. 'units.post': units_post_x,
  1010. 'ylabels.count': numXLabels,
  1011. 'strict': true
  1012. });
  1013. this.Set('chart.xmax', this.xscale2.max);
  1014. var interval = (ca.width - this.gutterLeft - this.gutterRight) / this.xscale2.labels.length;
  1015. for (var i=0,len=this.xscale2.labels.length; i<len; i+=1) {
  1016. var num = ( (prop['chart.xmax'] - prop['chart.xmin']) * ((i+1) / numXLabels)) + (xmin || 0);
  1017. var x = this.gutterLeft + ((i+1) * interval);
  1018. if (typeof(prop['chart.xscale.formatter']) == 'function') {
  1019. var text = String(prop['chart.xscale.formatter'](this, num));
  1020. } else {
  1021. var text = this.xscale2.labels[i]
  1022. }
  1023. RG.Text2(this, {'font':font,
  1024. 'size': text_size,
  1025. 'x': x,
  1026. 'y': y,
  1027. 'valign': 'center',
  1028. 'halign':'center',
  1029. 'text':text,
  1030. 'tag': 'xscale'
  1031. });
  1032. }
  1033. // If the Y axis is on the right hand side - draw the left most X label
  1034. if (prop['chart.yaxispos'] == 'right') {
  1035. RG.Text2(this, {'font':font,
  1036. 'size': text_size,
  1037. 'x': this.gutterLeft,
  1038. 'y': y,
  1039. 'valign': 'center',
  1040. 'halign':'center',
  1041. 'text':String(prop['chart.xmin']),
  1042. 'tag': 'xscale'
  1043. });
  1044. }
  1045. /**
  1046. * Draw X labels
  1047. */
  1048. } else {
  1049. // Put the text on the X axis
  1050. var graphArea = ca.width - this.gutterLeft - this.gutterRight;
  1051. var xInterval = graphArea / prop['chart.labels'].length;
  1052. var xPos = this.gutterLeft;
  1053. var yPos = (ca.height - this.gutterBottom) + 3;
  1054. var labels = prop['chart.labels'];
  1055. /**
  1056. * Text angle
  1057. */
  1058. var angle = 0;
  1059. var valign = 'top';
  1060. var halign = 'center';
  1061. if (prop['chart.text.angle'] > 0) {
  1062. angle = -1 * prop['chart.text.angle'];
  1063. valign = 'center';
  1064. halign = 'right';
  1065. yPos += 10;
  1066. }
  1067. for (i=0; i<labels.length; ++i) {
  1068. if (typeof(labels[i]) == 'object') {
  1069. if (prop['chart.labels.specific.align'] == 'center') {
  1070. var rightEdge = 0;
  1071. if (labels[i+1] && labels[i+1][1]) {
  1072. rightEdge = labels[i+1][1];
  1073. } else {
  1074. rightEdge = prop['chart.xmax'];
  1075. }
  1076. var offset = (this.getXCoord(rightEdge) - this.getXCoord(labels[i][1])) / 2;
  1077. } else {
  1078. var offset = 5;
  1079. }
  1080. RG.Text2(this, {'font':font,
  1081. 'size': prop['chart.text.size'],
  1082. 'x': this.getXCoord(labels[i][1]) + offset,
  1083. 'y': yPos,
  1084. 'valign': valign,
  1085. 'halign':angle != 0 ? 'right' : (prop['chart.labels.specific.align'] == 'center' ? 'center' : 'left'),
  1086. 'text':String(labels[i][0]),
  1087. 'angle':angle,
  1088. 'marker':false,
  1089. 'tag': 'labels.specific'
  1090. });
  1091. /**
  1092. * Draw the gray indicator line
  1093. */
  1094. co.beginPath();
  1095. co.strokeStyle = '#bbb';
  1096. co.moveTo(Math.round(this.gutterLeft + (graphArea * ((labels[i][1] - xMin)/ (prop['chart.xmax'] - xMin)))), ca.height - this.gutterBottom);
  1097. co.lineTo(Math.round(this.gutterLeft + (graphArea * ((labels[i][1] - xMin)/ (prop['chart.xmax'] - xMin)))), ca.height - this.gutterBottom + 20);
  1098. co.stroke();
  1099. } else {
  1100. RG.Text2(this, {'font':font,
  1101. 'size': prop['chart.text.size'],
  1102. 'x': xPos + (xInterval / 2),
  1103. 'y': yPos,
  1104. 'valign': valign,
  1105. 'halign':halign,
  1106. 'text':String(labels[i]),
  1107. 'angle':angle,
  1108. 'tag': 'labels'
  1109. });
  1110. }
  1111. // Do this for the next time around
  1112. xPos += xInterval;
  1113. }
  1114. /**
  1115. * Draw the final indicator line
  1116. */
  1117. if (typeof(labels[0]) == 'object') {
  1118. co.beginPath();
  1119. co.strokeStyle = '#bbb';
  1120. co.moveTo(this.gutterLeft + graphArea, ca.height - this.gutterBottom);
  1121. co.lineTo(this.gutterLeft + graphArea, ca.height - this.gutterBottom + 20);
  1122. co.stroke();
  1123. }
  1124. }
  1125. };
  1126. /**
  1127. * Draws the actual scatter graph marks
  1128. *
  1129. * @param i integer The dataset index
  1130. */
  1131. this.drawMarks =
  1132. this.DrawMarks = function (i)
  1133. {
  1134. /**
  1135. * Reset the coords array
  1136. */
  1137. this.coords[i] = [];
  1138. /**
  1139. * Plot the values
  1140. */
  1141. var xmax = prop['chart.xmax'];
  1142. var default_color = prop['chart.defaultcolor'];
  1143. for (var j=0,len=this.data[i].length; j<len; j+=1) {
  1144. /**
  1145. * This is here because tooltips are optional
  1146. */
  1147. var data_point = this.data[i];
  1148. var xCoord = data_point[j][0];
  1149. var yCoord = data_point[j][1];
  1150. var color = data_point[j][2] ? data_point[j][2] : default_color;
  1151. var tooltip = (data_point[j] && data_point[j][3]) ? data_point[j][3] : null;
  1152. this.DrawMark(
  1153. i,
  1154. xCoord,
  1155. yCoord,
  1156. xmax,
  1157. this.scale2.max,
  1158. color,
  1159. tooltip,
  1160. this.coords[i],
  1161. data_point,
  1162. j
  1163. );
  1164. }
  1165. };
  1166. /**
  1167. * Draws a single scatter mark
  1168. */
  1169. this.drawMark =
  1170. this.DrawMark = function (data_set_index, x, y, xMax, yMax, color, tooltip, coords, data, data_index)
  1171. {
  1172. var tickmarks = prop['chart.tickmarks'];
  1173. var tickSize = prop['chart.ticksize'];
  1174. var xMin = prop['chart.xmin'];
  1175. var x = ((x - xMin) / (xMax - xMin)) * (ca.width - this.gutterLeft - this.gutterRight);
  1176. var originalX = x;
  1177. var originalY = y;
  1178. /**
  1179. * This allows chart.tickmarks to be an array
  1180. */
  1181. if (tickmarks && typeof(tickmarks) == 'object') {
  1182. tickmarks = tickmarks[data_set_index];
  1183. }
  1184. /**
  1185. * This allows chart.ticksize to be an array
  1186. */
  1187. if (typeof(tickSize) == 'object') {
  1188. var tickSize = tickSize[data_set_index];
  1189. var halfTickSize = tickSize / 2;
  1190. } else {
  1191. var halfTickSize = tickSize / 2;
  1192. }
  1193. /**
  1194. * This bit is for boxplots only
  1195. */
  1196. if ( y
  1197. && typeof(y) == 'object'
  1198. && typeof(y[0]) == 'number'
  1199. && typeof(y[1]) == 'number'
  1200. && typeof(y[2]) == 'number'
  1201. && typeof(y[3]) == 'number'
  1202. && typeof(y[4]) == 'number'
  1203. ) {
  1204. //var yMin = prop['chart.ymin'] ? prop['chart.ymin'] : 0;
  1205. this.Set('chart.boxplot', true);
  1206. //this.graphheight = ca.height - this.gutterTop - this.gutterBottom;
  1207. //if (prop['chart.xaxispos'] == 'center') {
  1208. // this.graphheight /= 2;
  1209. //}
  1210. var y0 = this.getYCoord(y[0]);//(this.graphheight) - ((y[4] - yMin) / (yMax - yMin)) * (this.graphheight);
  1211. var y1 = this.getYCoord(y[1]);//(this.graphheight) - ((y[3] - yMin) / (yMax - yMin)) * (this.graphheight);
  1212. var y2 = this.getYCoord(y[2]);//(this.graphheight) - ((y[2] - yMin) / (yMax - yMin)) * (this.graphheight);
  1213. var y3 = this.getYCoord(y[3]);//(this.graphheight) - ((y[1] - yMin) / (yMax - yMin)) * (this.graphheight);
  1214. var y4 = this.getYCoord(y[4]);//(this.graphheight) - ((y[0] - yMin) / (yMax - yMin)) * (this.graphheight);
  1215. var col1 = y[5];
  1216. var col2 = y[6];
  1217. var boxWidth = typeof(y[7]) == 'number' ? y[7] : prop['chart.boxplot.width'];
  1218. //var y = this.graphheight - y2;
  1219. } else {
  1220. /**
  1221. * The new way of getting the Y coord. This function (should) handle everything
  1222. */
  1223. var yCoord = this.getYCoord(y);
  1224. }
  1225. //if (prop['chart.xaxispos'] == 'center'] {
  1226. // y /= 2;
  1227. // y += this.halfGraphHeight;
  1228. //
  1229. // if (prop['chart.ylabels.invert']) {
  1230. // p(y)
  1231. // }
  1232. //}
  1233. /**
  1234. * Account for the X axis being at the centre
  1235. */
  1236. // This is so that points are on the graph, and not the gutter - which helps
  1237. x += this.gutterLeft;
  1238. //y = ca.height - this.gutterBottom - y;
  1239. co.beginPath();
  1240. // Color
  1241. co.strokeStyle = color;
  1242. /**
  1243. * Boxplots
  1244. */
  1245. if (prop['chart.boxplot']) {
  1246. // boxWidth is a scale value, so convert it to a pixel vlue
  1247. boxWidth = (boxWidth / prop['chart.xmax']) * (ca.width -this.gutterLeft - this.gutterRight);
  1248. var halfBoxWidth = boxWidth / 2;
  1249. if (prop['chart.line.visible']) {
  1250. co.beginPath();
  1251. co.strokeRect(x - halfBoxWidth, y1, boxWidth, y3 - y1);
  1252. // Draw the upper coloured box if a value is specified
  1253. if (col1) {
  1254. co.fillStyle = col1;
  1255. co.fillRect(x - halfBoxWidth, y1, boxWidth, y2 - y1);
  1256. }
  1257. // Draw the lower coloured box if a value is specified
  1258. if (col2) {
  1259. co.fillStyle = col2;
  1260. co.fillRect(x - halfBoxWidth, y2, boxWidth, y3 - y2);
  1261. }
  1262. co.stroke();
  1263. // Now draw the whiskers
  1264. co.beginPath();
  1265. if (prop['chart.boxplot.capped']) {
  1266. co.moveTo(x - halfBoxWidth, Math.round(y0));
  1267. co.lineTo(x + halfBoxWidth, Math.round(y0));
  1268. }
  1269. co.moveTo(Math.round(x), y0);
  1270. co.lineTo(Math.round(x), y1);
  1271. if (prop['chart.boxplot.capped']) {
  1272. co.moveTo(x - halfBoxWidth, Math.round(y4));
  1273. co.lineTo(x + halfBoxWidth, Math.round(y4));
  1274. }
  1275. co.moveTo(Math.round(x), y4);
  1276. co.lineTo(Math.round(x), y3);
  1277. co.stroke();
  1278. }
  1279. }
  1280. /**
  1281. * Draw the tickmark, but not for boxplots
  1282. */
  1283. if (prop['chart.line.visible'] && typeof(y) == 'number' && !y0 && !y1 && !y2 && !y3 && !y4) {
  1284. if (tickmarks == 'circle') {
  1285. co.arc(x, yCoord, halfTickSize, 0, 6.28, 0);
  1286. co.fillStyle = color;
  1287. co.fill();
  1288. } else if (tickmarks == 'plus') {
  1289. co.moveTo(x, yCoord - halfTickSize);
  1290. co.lineTo(x, yCoord + halfTickSize);
  1291. co.moveTo(x - halfTickSize, yCoord);
  1292. co.lineTo(x + halfTickSize, yCoord);
  1293. co.stroke();
  1294. } else if (tickmarks == 'square') {
  1295. co.strokeStyle = color;
  1296. co.fillStyle = color;
  1297. co.fillRect(
  1298. x - halfTickSize,
  1299. yCoord - halfTickSize,
  1300. tickSize,
  1301. tickSize
  1302. );
  1303. //co.fill();
  1304. } else if (tickmarks == 'cross') {
  1305. co.moveTo(x - halfTickSize, yCoord - halfTickSize);
  1306. co.lineTo(x + halfTickSize, yCoord + halfTickSize);
  1307. co.moveTo(x + halfTickSize, yCoord - halfTickSize);
  1308. co.lineTo(x - halfTickSize, yCoord + halfTickSize);
  1309. co.stroke();
  1310. /**
  1311. * Diamond shape tickmarks
  1312. */
  1313. } else if (tickmarks == 'diamond') {
  1314. co.fillStyle = co.strokeStyle;
  1315. co.moveTo(x, yCoord - halfTickSize);
  1316. co.lineTo(x + halfTickSize, yCoord);
  1317. co.lineTo(x, yCoord + halfTickSize);
  1318. co.lineTo(x - halfTickSize, yCoord);
  1319. co.lineTo(x, yCoord - halfTickSize);
  1320. co.fill();
  1321. co.stroke();
  1322. /**
  1323. * Custom tickmark style
  1324. */
  1325. } else if (typeof(tickmarks) == 'function') {
  1326. var graphWidth = ca.width - this.gutterLeft - this.gutterRight
  1327. var graphheight = ca.height - this.gutterTop - this.gutterBottom;
  1328. var xVal = ((x - this.gutterLeft) / graphWidth) * xMax;
  1329. var yVal = ((graphheight - (yCoord - this.gutterTop)) / graphheight) * yMax;
  1330. tickmarks(this, data, x, yCoord, xVal, yVal, xMax, yMax, color, data_set_index, data_index)
  1331. /**
  1332. * No tickmarks
  1333. */
  1334. } else if (tickmarks === null) {
  1335. /**
  1336. * Unknown tickmark type
  1337. */
  1338. } else {
  1339. alert('[SCATTER] (' + this.id + ') Unknown tickmark style: ' + tickmarks );
  1340. }
  1341. }
  1342. /**
  1343. * Add the tickmark to the coords array
  1344. */
  1345. if ( prop['chart.boxplot']
  1346. && typeof y0 === 'number'
  1347. && typeof y1 === 'number'
  1348. && typeof y2 === 'number'
  1349. && typeof y3 === 'number'
  1350. && typeof y4 === 'number') {
  1351. x = [x - halfBoxWidth, x + halfBoxWidth];
  1352. yCoord = [y0, y1, y2, y3, y4];
  1353. }
  1354. coords.push([x, yCoord, tooltip]);
  1355. };
  1356. /**
  1357. * Draws an optional line connecting the tick marks.
  1358. *
  1359. * @param i The index of the dataset to use
  1360. */
  1361. this.drawLine =
  1362. this.DrawLine = function (i)
  1363. {
  1364. if (typeof(prop['chart.line.visible']) == 'boolean' && prop['chart.line.visible'] == false) {
  1365. return;
  1366. }
  1367. if (prop['chart.line'] && this.coords[i].length >= 2) {
  1368. if (prop['chart.line.dash'] && typeof co.setLineDash === 'function') {
  1369. co.setLineDash(prop['chart.line.dash']);
  1370. }
  1371. co.lineCap = 'round';
  1372. co.lineJoin = 'round';
  1373. co.lineWidth = this.getLineWidth(i);// i is the index of the set of coordinates
  1374. co.strokeStyle = prop['chart.line.colors'][i];
  1375. co.beginPath();
  1376. var prevY = null;
  1377. var currY = null;
  1378. for (var j=0,len=this.coords[i].length; j<len; j+=1) {
  1379. var xPos = this.coords[i][j][0];
  1380. var yPos = this.coords[i][j][1];
  1381. if (j > 0) prevY = this.coords[i][j - 1][1];
  1382. currY = yPos;
  1383. if (j == 0 || RG.is_null(prevY) || RG.is_null(currY)) {
  1384. co.moveTo(xPos, yPos);
  1385. } else {
  1386. // Stepped?
  1387. var stepped = prop['chart.line.stepped'];
  1388. if ( (typeof stepped == 'boolean' && stepped)
  1389. || (typeof stepped == 'object' && stepped[i])
  1390. ) {
  1391. co.lineTo(this.coords[i][j][0], this.coords[i][j - 1][1]);
  1392. }
  1393. co.lineTo(xPos, yPos);
  1394. }
  1395. }
  1396. co.stroke();
  1397. /**
  1398. * Set the linedash back to the default
  1399. */
  1400. if (prop['chart.line.dash'] && typeof co.setLineDash === 'function') {
  1401. co.setLineDash([1,0]);
  1402. }
  1403. }
  1404. /**
  1405. * Set the linewidth back to 1
  1406. */
  1407. co.lineWidth = 1;
  1408. };
  1409. /**
  1410. * Returns the linewidth
  1411. *
  1412. * @param number i The index of the "line" (/set of coordinates)
  1413. */
  1414. this.getLineWidth =
  1415. this.GetLineWidth = function (i)
  1416. {
  1417. var linewidth = prop['chart.line.linewidth'];
  1418. if (typeof linewidth == 'number') {
  1419. return linewidth;
  1420. } else if (typeof linewidth == 'object') {
  1421. if (linewidth[i]) {
  1422. return linewidth[i];
  1423. } else {
  1424. return linewidth[0];
  1425. }
  1426. alert('[SCATTER] Error! chart.linewidth should be a single number or an array of one or more numbers');
  1427. }
  1428. };
  1429. /**
  1430. * Draws vertical bars. Line chart doesn't use a horizontal scale, hence this function
  1431. * is not common
  1432. */
  1433. this.drawVBars =
  1434. this.DrawVBars = function ()
  1435. {
  1436. var vbars = prop['chart.background.vbars'];
  1437. var graphWidth = ca.width - this.gutterLeft - this.gutterRight;
  1438. if (vbars) {
  1439. var xmax = prop['chart.xmax'];
  1440. var xmin = prop['chart.xmin'];
  1441. for (var i=0,len=vbars.length; i<len; i+=1) {
  1442. var key = i;
  1443. var value = vbars[key];
  1444. /**
  1445. * Accomodate date/time values
  1446. */
  1447. if (typeof value[0] == 'string') value[0] = RG.parseDate(value[0]);
  1448. if (typeof value[1] == 'string') value[1] = RG.parseDate(value[1]) - value[0];
  1449. var x = (( (value[0] - xmin) / (xmax - xmin) ) * graphWidth) + this.gutterLeft;
  1450. var width = (value[1] / (xmax - xmin) ) * graphWidth;
  1451. co.fillStyle = value[2];
  1452. co.fillRect(x, this.gutterTop, width, (ca.height - this.gutterTop - this.gutterBottom));
  1453. }
  1454. }
  1455. };
  1456. /**
  1457. * Draws in-graph labels.
  1458. *
  1459. * @param object obj The graph object
  1460. */
  1461. this.drawInGraphLabels =
  1462. this.DrawInGraphLabels = function (obj)
  1463. {
  1464. var labels = obj.Get('chart.labels.ingraph');
  1465. var labels_processed = [];
  1466. if (!labels) {
  1467. return;
  1468. }
  1469. // Defaults
  1470. var fgcolor = 'black';
  1471. var bgcolor = 'white';
  1472. var direction = 1;
  1473. /**
  1474. * Preprocess the labels array. Numbers are expanded
  1475. */
  1476. for (var i=0,len=labels.length; i<len; i+=1) {
  1477. if (typeof(labels[i]) == 'number') {
  1478. for (var j=0; j<labels[i]; ++j) {
  1479. labels_processed.push(null);
  1480. }
  1481. } else if (typeof(labels[i]) == 'string' || typeof(labels[i]) == 'object') {
  1482. labels_processed.push(labels[i]);
  1483. } else {
  1484. labels_processed.push('');
  1485. }
  1486. }
  1487. /**
  1488. * Turn off any shadow
  1489. */
  1490. RG.NoShadow(obj);
  1491. if (labels_processed && labels_processed.length > 0) {
  1492. var i=0;
  1493. for (var set=0; set<obj.coords.length; ++set) {
  1494. for (var point = 0; point<obj.coords[set].length; ++point) {
  1495. if (labels_processed[i]) {
  1496. var x = obj.coords[set][point][0];
  1497. var y = obj.coords[set][point][1];
  1498. var length = typeof(labels_processed[i][4]) == 'number' ? labels_processed[i][4] : 25;
  1499. var text_x = x;
  1500. var text_y = y - 5 - length;
  1501. co.moveTo(x, y - 5);
  1502. co.lineTo(x, y - 5 - length);
  1503. co.stroke();
  1504. co.beginPath();
  1505. // This draws the arrow
  1506. co.moveTo(x, y - 5);
  1507. co.lineTo(x - 3, y - 10);
  1508. co.lineTo(x + 3, y - 10);
  1509. co.closePath();
  1510. co.beginPath();
  1511. // Fore ground color
  1512. co.fillStyle = (typeof(labels_processed[i]) == 'object' && typeof(labels_processed[i][1]) == 'string') ? labels_processed[i][1] : 'black';
  1513. RG.Text2(this, {
  1514. 'font':obj.Get('chart.text.font'),
  1515. 'size':obj.Get('chart.text.size'),
  1516. 'x':text_x,
  1517. 'y':text_y,
  1518. 'text':(typeof(labels_processed[i]) == 'object' && typeof(labels_processed[i][0]) == 'string') ? labels_processed[i][0] : labels_processed[i],
  1519. 'valign':'bottom',
  1520. 'halign':'center',
  1521. 'bounding':true,
  1522. 'boundingFill':(typeof(labels_processed[i]) == 'object' && typeof(labels_processed[i][2]) == 'string') ? labels_processed[i][2] : 'white',
  1523. 'tag': 'labels.ingraph'
  1524. });
  1525. co.fill();
  1526. }
  1527. i++;
  1528. }
  1529. }
  1530. }
  1531. };
  1532. /**
  1533. * This function makes it much easier to get the (if any) point that is currently being hovered over.
  1534. *
  1535. * @param object e The event object
  1536. */
  1537. this.getShape =
  1538. this.getPoint = function (e)
  1539. {
  1540. var mouseXY = RG.getMouseXY(e);
  1541. var mouseX = mouseXY[0];
  1542. var mouseY = mouseXY[1];
  1543. var overHotspot = false;
  1544. var offset = prop['chart.tooltips.hotspot']; // This is how far the hotspot extends
  1545. for (var set=0,len=this.coords.length; set<len; ++set) {
  1546. for (var i=0,len2=this.coords[set].length; i<len2; ++i) {
  1547. var x = this.coords[set][i][0];
  1548. var y = this.coords[set][i][1];
  1549. var tooltip = this.data[set][i][3];
  1550. if (typeof(y) == 'number') {
  1551. if (mouseX <= (x + offset) &&
  1552. mouseX >= (x - offset) &&
  1553. mouseY <= (y + offset) &&
  1554. mouseY >= (y - offset)) {
  1555. var tooltip = RG.parseTooltipText(this.data[set][i][3], 0);
  1556. var index_adjusted = i;
  1557. for (var ds=(set-1); ds >=0; --ds) {
  1558. index_adjusted += this.data[ds].length;
  1559. }
  1560. return {
  1561. 0: this, 1: x, 2: y, 3: set, 4: i, 5: this.data[set][i][3],
  1562. 'object': this, 'x': x, 'y': y, 'dataset': set, 'index': i, 'tooltip': tooltip, 'index_adjusted': index_adjusted
  1563. };
  1564. }
  1565. } else if (RG.is_null(y)) {
  1566. // Nothing to see here
  1567. } else {
  1568. var mark = this.data[set][i];
  1569. /**
  1570. * Determine the width
  1571. */
  1572. var width = prop['chart.boxplot.width'];
  1573. if (typeof(mark[1][7]) == 'number') {
  1574. width = mark[1][7];
  1575. }
  1576. if ( typeof(x) == 'object'
  1577. && mouseX > x[0]
  1578. && mouseX < x[1]
  1579. && mouseY < y[1]
  1580. && mouseY > y[3]
  1581. ) {
  1582. var tooltip = RG.parseTooltipText(this.data[set][i][3], 0);
  1583. return {
  1584. 0: this, 1: x[0], 2: x[1] - x[0], 3: y[1], 4: y[3] - y[1], 5: set, 6: i, 7: this.data[set][i][3],
  1585. 'object': this, 'x': x[0], 'y': y[1], 'width': x[1] - x[0], 'height': y[3] - y[1], 'dataset': set, 'index': i, 'tooltip': tooltip
  1586. };
  1587. }
  1588. }
  1589. }
  1590. }
  1591. };
  1592. /**
  1593. * Draws the above line labels
  1594. */
  1595. this.drawAboveLabels =
  1596. this.DrawAboveLabels = function ()
  1597. {
  1598. var size = prop['chart.labels.above.size'];
  1599. var font = prop['chart.text.font'];
  1600. var units_pre = prop['chart.units.pre'];
  1601. var units_post = prop['chart.units.post'];
  1602. for (var set=0,len=this.coords.length; set<len; ++set) {
  1603. for (var point=0,len2=this.coords[set].length; point<len2; ++point) {
  1604. var x_val = this.data[set][point][0];
  1605. var y_val = this.data[set][point][1];
  1606. if (!RG.is_null(y_val)) {
  1607. // Use the top most value from a box plot
  1608. if (RG.is_array(y_val)) {
  1609. var max = 0;
  1610. for (var i=0; i<y_val; ++i) {
  1611. max = Math.max(max, y_val[i]);
  1612. }
  1613. y_val = max;
  1614. }
  1615. var x_pos = this.coords[set][point][0];
  1616. var y_pos = this.coords[set][point][1];
  1617. RG.Text2(this, {
  1618. 'font':font,
  1619. 'size':size,
  1620. 'x':x_pos,
  1621. 'y':y_pos - 5 - size,
  1622. 'text':x_val.toFixed(prop['chart.labels.above.decimals']) + ', ' + y_val.toFixed(prop['chart.labels.above.decimals']),
  1623. 'valign':'center',
  1624. 'halign':'center',
  1625. 'bounding':true,
  1626. 'boundingFill':'rgba(255, 255, 255, 0.7)',
  1627. 'tag': 'labels.above'
  1628. });
  1629. }
  1630. }
  1631. }
  1632. };
  1633. /**
  1634. * When you click on the chart, this method can return the Y value at that point. It works for any point on the
  1635. * chart (that is inside the gutters) - not just points within the Bars.
  1636. *
  1637. * @param object e The event object
  1638. */
  1639. this.getYValue =
  1640. this.getValue = function (arg)
  1641. {
  1642. if (arg.length == 2) {
  1643. var mouseX = arg[0];
  1644. var mouseY = arg[1];
  1645. } else {
  1646. var mouseCoords = RG.getMouseXY(arg);
  1647. var mouseX = mouseCoords[0];
  1648. var mouseY = mouseCoords[1];
  1649. }
  1650. var obj = this;
  1651. if ( mouseY < this.gutterTop
  1652. || mouseY > (ca.height - this.gutterBottom)
  1653. || mouseX < this.gutterLeft
  1654. || mouseX > (ca.width - this.gutterRight)
  1655. ) {
  1656. return null;
  1657. }
  1658. if (prop['chart.xaxispos'] == 'center') {
  1659. var value = (((this.grapharea / 2) - (mouseY - this.gutterTop)) / this.grapharea) * (this.max - this.min)
  1660. value *= 2;
  1661. // Account for each side of the X axis
  1662. if (value >= 0) {
  1663. value += this.min
  1664. if (prop['chart.ylabels.invert']) {
  1665. value -= this.min;
  1666. value = this.max - value;
  1667. }
  1668. } else {
  1669. value -= this.min;
  1670. if (prop['chart.ylabels.invert']) {
  1671. value += this.min;
  1672. value = this.max + value;
  1673. value *= -1;
  1674. }
  1675. }
  1676. } else {
  1677. var value = ((this.grapharea - (mouseY - this.gutterTop)) / this.grapharea) * (this.max - this.min)
  1678. value += this.min;
  1679. if (prop['chart.ylabels.invert']) {
  1680. value -= this.min;
  1681. value = this.max - value;
  1682. }
  1683. }
  1684. return value;
  1685. };
  1686. /**
  1687. * When you click on the chart, this method can return the X value at that point.
  1688. *
  1689. * @param mixed arg This can either be an event object or the X coordinate
  1690. * @param number If specifying the X coord as the first arg then this should be the Y coord
  1691. */
  1692. this.getXValue = function (arg)
  1693. {
  1694. if (arg.length == 2) {
  1695. var mouseX = arg[0];
  1696. var mouseY = arg[1];
  1697. } else {
  1698. var mouseXY = RG.getMouseXY(arg);
  1699. var mouseX = mouseXY[0];
  1700. var mouseY = mouseXY[1];
  1701. }
  1702. var obj = this;
  1703. if ( mouseY < this.gutterTop
  1704. || mouseY > (ca.height - this.gutterBottom)
  1705. || mouseX < this.gutterLeft
  1706. || mouseX > (ca.width - this.gutterRight)
  1707. ) {
  1708. return null;
  1709. }
  1710. var width = (ca.width - this.gutterLeft - this.gutterRight);
  1711. var value = ((mouseX - this.gutterLeft) / width) * (prop['chart.xmax'] - prop['chart.xmin'])
  1712. value += prop['chart.xmin'];
  1713. return value;
  1714. };
  1715. /**
  1716. * Each object type has its own Highlight() function which highlights the appropriate shape
  1717. *
  1718. * @param object shape The shape to highlight
  1719. */
  1720. this.highlight =
  1721. this.Highlight = function (shape)
  1722. {
  1723. // Boxplot highlight
  1724. if (shape['height']) {
  1725. RG.Highlight.Rect(this, shape);
  1726. // Point highlight
  1727. } else {
  1728. RG.Highlight.Point(this, shape);
  1729. }
  1730. };
  1731. /**
  1732. * The getObjectByXY() worker method. Don't call this call:
  1733. *
  1734. * RG.ObjectRegistry.getObjectByXY(e)
  1735. *
  1736. * @param object e The event object
  1737. */
  1738. this.getObjectByXY = function (e)
  1739. {
  1740. var mouseXY = RG.getMouseXY(e);
  1741. if (
  1742. mouseXY[0] > (this.gutterLeft - 3)
  1743. && mouseXY[0] < (ca.width - this.gutterRight + 3)
  1744. && mouseXY[1] > (this.gutterTop - 3)
  1745. && mouseXY[1] < ((ca.height - this.gutterBottom) + 3)
  1746. ) {
  1747. return this;
  1748. }
  1749. };
  1750. /**
  1751. * This function can be used when the canvas is clicked on (or similar - depending on the event)
  1752. * to retrieve the relevant X coordinate for a particular value.
  1753. *
  1754. * @param int value The value to get the X coordinate for
  1755. */
  1756. this.getXCoord = function (value)
  1757. {
  1758. if (typeof value != 'number' && typeof value != 'string') {
  1759. return null;
  1760. }
  1761. // Allow for date strings to be passed
  1762. if (typeof value === 'string') {
  1763. value = RG.parseDate(value);
  1764. }
  1765. var xmin = prop['chart.xmin'];
  1766. var xmax = prop['chart.xmax'];
  1767. var x;
  1768. if (value < xmin) return null;
  1769. if (value > xmax) return null;
  1770. var gutterRight = this.gutterRight;
  1771. var gutterLeft = this.gutterLeft;
  1772. if (prop['chart.yaxispos'] == 'right') {
  1773. x = ((value - xmin) / (xmax - xmin)) * (ca.width - gutterLeft - gutterRight);
  1774. x = (ca.width - gutterRight - x);
  1775. } else {
  1776. x = ((value - xmin) / (xmax - xmin)) * (ca.width - gutterLeft - gutterRight);
  1777. x = x + gutterLeft;
  1778. }
  1779. return x;
  1780. };
  1781. /**
  1782. * This function positions a tooltip when it is displayed
  1783. *
  1784. * @param obj object The chart object
  1785. * @param int x The X coordinate specified for the tooltip
  1786. * @param int y The Y coordinate specified for the tooltip
  1787. * @param objec tooltip The tooltips DIV element
  1788. */
  1789. this.positionTooltip = function (obj, x, y, tooltip, idx)
  1790. {
  1791. var shape = RG.Registry.Get('chart.tooltip.shape');
  1792. var dataset = shape['dataset'];
  1793. var index = shape['index'];
  1794. var coordX = obj.coords[dataset][index][0]
  1795. var coordY = obj.coords[dataset][index][1]
  1796. var canvasXY = RG.getCanvasXY(obj.canvas);
  1797. var gutterLeft = obj.gutterLeft;
  1798. var gutterTop = obj.gutterTop;
  1799. var width = tooltip.offsetWidth;
  1800. var height = tooltip.offsetHeight;
  1801. tooltip.style.left = 0;
  1802. tooltip.style.top = 0;
  1803. // Is the coord a boxplot
  1804. var isBoxplot = typeof(coordY) == 'object' ? true : false;
  1805. // Show any overflow (ie the arrow)
  1806. tooltip.style.overflow = '';
  1807. // Create the arrow
  1808. var img = new Image();
  1809. img.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAAFCAYAAACjKgd3AAAARUlEQVQYV2NkQAN79+797+RkhC4M5+/bd47B2dmZEVkBCgcmgcsgbAaA9GA1BCSBbhAuA/AagmwQPgMIGgIzCD0M0AMMAEFVIAa6UQgcAAAAAElFTkSuQmCC';
  1810. img.style.position = 'absolute';
  1811. img.id = '__rgraph_tooltip_pointer__';
  1812. img.style.top = (tooltip.offsetHeight - 2) + 'px';
  1813. tooltip.appendChild(img);
  1814. // Reposition the tooltip if at the edges:
  1815. // LEFT edge //////////////////////////////////////////////////////////////////
  1816. if ((canvasXY[0] + (coordX[0] || coordX) - (width / 2)) < 10) {
  1817. if (isBoxplot) {
  1818. tooltip.style.left = canvasXY[0] + coordX[0] + ((coordX[1] - coordX[0]) / 2) - (width * 0.1) + 'px';
  1819. tooltip.style.top = canvasXY[1] + coordY[2] - height - 5 + 'px';
  1820. img.style.left = ((width * 0.1) - 8.5) + 'px';
  1821. } else {
  1822. tooltip.style.left = (canvasXY[0] + coordX - (width * 0.1)) + 'px';
  1823. tooltip.style.top = canvasXY[1] + coordY - height - 9 + 'px';
  1824. img.style.left = ((width * 0.1) - 8.5) + 'px';
  1825. }
  1826. // RIGHT edge //////////////////////////////////////////////////////////////////
  1827. } else if ((canvasXY[0] + (coordX[0] || coordX) + (width / 2)) > doc.body.offsetWidth) {
  1828. if (isBoxplot) {
  1829. tooltip.style.left = canvasXY[0] + coordX[0] + ((coordX[1] - coordX[0]) / 2) - (width * 0.9) + 'px';
  1830. tooltip.style.top = canvasXY[1] + coordY[2] - height - 5 + 'px';
  1831. img.style.left = ((width * 0.9) - 8.5) + 'px';
  1832. } else {
  1833. tooltip.style.left = (canvasXY[0] + coordX - (width * 0.9)) + 'px';
  1834. tooltip.style.top = canvasXY[1] + coordY - height - 9 + 'px';
  1835. img.style.left = ((width * 0.9) - 8.5) + 'px';
  1836. }
  1837. // Default positioning - CENTERED //////////////////////////////////////////////////////////////////
  1838. } else {
  1839. if (isBoxplot) {
  1840. tooltip.style.left = canvasXY[0] + coordX[0] + ((coordX[1] - coordX[0]) / 2) - (width / 2) + 'px';
  1841. tooltip.style.top = canvasXY[1] + coordY[2] - height - 5 + 'px';
  1842. img.style.left = ((width * 0.5) - 8.5) + 'px';
  1843. } else {
  1844. tooltip.style.left = (canvasXY[0] + coordX - (width * 0.5)) + 'px';
  1845. tooltip.style.top = canvasXY[1] + coordY - height - 9 + 'px';
  1846. img.style.left = ((width * 0.5) - 8.5) + 'px';
  1847. }
  1848. }
  1849. };
  1850. /**
  1851. * Returns the applicable Y COORDINATE when given a Y value
  1852. *
  1853. * @param int value The value to use
  1854. * @return int The appropriate Y coordinate
  1855. */
  1856. this.getYCoord =
  1857. this.getYCoordFromValue = function (value)
  1858. {
  1859. if (typeof(value) != 'number') {
  1860. return null;
  1861. }
  1862. var invert = prop['chart.ylabels.invert'];
  1863. var xaxispos = prop['chart.xaxispos'];
  1864. var graphHeight = ca.height - this.gutterTop - this.gutterBottom;
  1865. var halfGraphHeight = graphHeight / 2;
  1866. var ymax = this.max;
  1867. var ymin = prop['chart.ymin'];
  1868. var coord = 0;
  1869. if (value > ymax || (prop['chart.xaxispos'] == 'bottom' && value < ymin) || (prop['chart.xaxispos'] == 'center' && ((value > 0 && value < ymin) || (value < 0 && value > (-1 * ymin))))) {
  1870. return null;
  1871. }
  1872. /**
  1873. * This calculates scale values if the X axis is in the center
  1874. */
  1875. if (xaxispos == 'center') {
  1876. coord = ((Math.abs(value) - ymin) / (ymax - ymin)) * halfGraphHeight;
  1877. if (invert) {
  1878. coord = halfGraphHeight - coord;
  1879. }
  1880. if (value < 0) {
  1881. coord += this.gutterTop;
  1882. coord += halfGraphHeight;
  1883. } else {
  1884. coord = halfGraphHeight - coord;
  1885. coord += this.gutterTop;
  1886. }
  1887. /**
  1888. * And this calculates scale values when the X axis is at the bottom
  1889. */
  1890. } else {
  1891. coord = ((value - ymin) / (ymax - ymin)) * graphHeight;
  1892. if (invert) {
  1893. coord = graphHeight - coord;
  1894. }
  1895. // Invert the coordinate because the Y scale starts at the top
  1896. coord = graphHeight - coord;
  1897. // And add on the top gutter
  1898. coord = this.gutterTop + coord;
  1899. }
  1900. return coord;
  1901. };
  1902. /**
  1903. * A helper class that helps facilitatesbubble charts
  1904. */
  1905. RG.Scatter.Bubble = function (scatter, min, max, width, data)
  1906. {
  1907. this.scatter = scatter;
  1908. this.min = min;
  1909. this.max = max;
  1910. this.width = width;
  1911. this.data = data;
  1912. /**
  1913. * A setter for the Bubble chart class - it just acts as a "passthru" to the Scatter object
  1914. */
  1915. this.set =
  1916. this.Set = function (name, value)
  1917. {
  1918. this.scatter.Set(name, value);
  1919. return this;
  1920. };
  1921. /**
  1922. * A getter for the Bubble chart class - it just acts as a "passthru" to the Scatter object
  1923. */
  1924. this.get =
  1925. this.Get = function (name)
  1926. {
  1927. this.scatter.Get(name);
  1928. };
  1929. /**
  1930. * Tha Bubble chart draw function
  1931. */
  1932. this.draw =
  1933. this.Draw = function ()
  1934. {
  1935. var bubble_min = this.min;
  1936. var bubble_max = this.max;
  1937. var bubble_data = this.data;
  1938. var bubble_max_width = this.width;
  1939. // This custom ondraw event listener draws the bubbles
  1940. this.scatter.ondraw = function (obj)
  1941. {
  1942. // Loop through all the points (first dataset)
  1943. for (var i=0; i<obj.coords[0].length; ++i) {
  1944. bubble_data[i] = Math.max(bubble_data[i], bubble_min);
  1945. bubble_data[i] = Math.min(bubble_data[i], bubble_max);
  1946. var r = ((bubble_data[i] - bubble_min) / (bubble_max - bubble_min) ) * bubble_max_width;
  1947. co.beginPath();
  1948. co.fillStyle = RG.RadialGradient(obj,
  1949. obj.coords[0][i][0] + (r / 2.5),
  1950. obj.coords[0][i][1] - (r / 2.5),
  1951. 0,
  1952. obj.coords[0][i][0] + (r / 2.5),
  1953. obj.coords[0][i][1] - (r / 2.5),
  1954. 50,
  1955. 'white',
  1956. obj.data[0][i][2] ? obj.data[0][i][2] : obj.properties['chart.defaultcolor']
  1957. );
  1958. co.arc(obj.coords[0][i][0], obj.coords[0][i][1], r, 0, RG.TWOPI, false);
  1959. co.fill();
  1960. }
  1961. }
  1962. return this.scatter.Draw();
  1963. };
  1964. };
  1965. /**
  1966. * This allows for easy specification of gradients
  1967. */
  1968. this.parseColors = function ()
  1969. {
  1970. // Save the original colors so that they can be restored when the canvas is reset
  1971. if (this.original_colors.length === 0) {
  1972. this.original_colors['data'] = RG.array_clone(this.data);
  1973. this.original_colors['chart.background.vbars'] = RG.array_clone(prop['chart.background.vbars']);
  1974. this.original_colors['chart.background.hbars'] = RG.array_clone(prop['chart.background.hbars']);
  1975. this.original_colors['chart.line.colors'] = RG.array_clone(prop['chart.line.colors']);
  1976. this.original_colors['chart.defaultcolor'] = RG.array_clone(prop['chart.defaultcolor']);
  1977. this.original_colors['chart.crosshairs.color'] = RG.array_clone(prop['chart.crosshairs.color']);
  1978. this.original_colors['chart.highlight.stroke'] = RG.array_clone(prop['chart.highlight.stroke']);
  1979. this.original_colors['chart.highlight.fill'] = RG.array_clone(prop['chart.highlight.fill']);
  1980. this.original_colors['chart.background.barcolor1'] = RG.array_clone(prop['chart.background.barcolor1']);
  1981. this.original_colors['chart.background.barcolor2'] = RG.array_clone(prop['chart.background.barcolor2']);
  1982. this.original_colors['chart.background.grid.color'] = RG.array_clone(prop['chart.background.grid.color']);
  1983. this.original_colors['chart.background.color'] = RG.array_clone(prop['chart.background.color']);
  1984. this.original_colors['chart.axis.color'] = RG.array_clone(prop['chart.axis.color']);
  1985. }
  1986. // Colors
  1987. var data = this.data;
  1988. if (data) {
  1989. for (var dataset=0; dataset<data.length; ++dataset) {
  1990. for (var i=0; i<this.data[dataset].length; ++i) {
  1991. // Boxplots
  1992. if (typeof(this.data[dataset][i][1]) == 'object' && this.data[dataset][i][1]) {
  1993. if (typeof(this.data[dataset][i][1][5]) == 'string') this.data[dataset][i][1][5] = this.parseSingleColorForGradient(this.data[dataset][i][1][5]);
  1994. if (typeof(this.data[dataset][i][1][6]) == 'string') this.data[dataset][i][1][6] = this.parseSingleColorForGradient(this.data[dataset][i][1][6]);
  1995. }
  1996. this.data[dataset][i][2] = this.parseSingleColorForGradient(this.data[dataset][i][2]);
  1997. }
  1998. }
  1999. }
  2000. // Parse HBars
  2001. var hbars = prop['chart.background.hbars'];
  2002. if (hbars) {
  2003. for (i=0; i<hbars.length; ++i) {
  2004. hbars[i][2] = this.parseSingleColorForGradient(hbars[i][2]);
  2005. }
  2006. }
  2007. // Parse HBars
  2008. var vbars = prop['chart.background.vbars'];
  2009. if (vbars) {
  2010. for (i=0; i<vbars.length; ++i) {
  2011. vbars[i][2] = this.parseSingleColorForGradient(vbars[i][2]);
  2012. }
  2013. }
  2014. // Parse line colors
  2015. var colors = prop['chart.line.colors'];
  2016. if (colors) {
  2017. for (i=0; i<colors.length; ++i) {
  2018. colors[i] = this.parseSingleColorForGradient(colors[i]);
  2019. }
  2020. }
  2021. prop['chart.defaultcolor'] = this.parseSingleColorForGradient(prop['chart.defaultcolor']);
  2022. prop['chart.crosshairs.color'] = this.parseSingleColorForGradient(prop['chart.crosshairs.color']);
  2023. prop['chart.highlight.stroke'] = this.parseSingleColorForGradient(prop['chart.highlight.stroke']);
  2024. prop['chart.highlight.fill'] = this.parseSingleColorForGradient(prop['chart.highlight.fill']);
  2025. prop['chart.background.barcolor1'] = this.parseSingleColorForGradient(prop['chart.background.barcolor1']);
  2026. prop['chart.background.barcolor2'] = this.parseSingleColorForGradient(prop['chart.background.barcolor2']);
  2027. prop['chart.background.grid.color'] = this.parseSingleColorForGradient(prop['chart.background.grid.color']);
  2028. prop['chart.background.color'] = this.parseSingleColorForGradient(prop['chart.background.color']);
  2029. prop['chart.axis.color'] = this.parseSingleColorForGradient(prop['chart.axis.color']);
  2030. };
  2031. /**
  2032. * This parses a single color value for a gradient
  2033. */
  2034. this.parseSingleColorForGradient = function (color)
  2035. {
  2036. if (!color || typeof(color) != 'string') {
  2037. return color;
  2038. }
  2039. if (color.match(/^gradient\((.*)\)$/i)) {
  2040. var parts = RegExp.$1.split(':');
  2041. // Create the gradient
  2042. var grad = co.createLinearGradient(0,ca.height - prop['chart.gutter.bottom'], 0, prop['chart.gutter.top']);
  2043. var diff = 1 / (parts.length - 1);
  2044. grad.addColorStop(0, RG.trim(parts[0]));
  2045. for (var j=1; j<parts.length; ++j) {
  2046. grad.addColorStop(j * diff, RG.trim(parts[j]));
  2047. }
  2048. }
  2049. return grad ? grad : color;
  2050. };
  2051. /**
  2052. * This function handles highlighting an entire data-series for the interactive
  2053. * key
  2054. *
  2055. * @param int index The index of the data series to be highlighted
  2056. */
  2057. this.interactiveKeyHighlight = function (index)
  2058. {
  2059. if (this.coords && this.coords[index] && this.coords[index].length) {
  2060. this.coords[index].forEach(function (value, idx, arr)
  2061. {
  2062. co.beginPath();
  2063. co.fillStyle = prop['chart.key.interactive.highlight.chart.fill'];
  2064. co.arc(value[0], value[1], prop['chart.ticksize'] + 3, 0, RG.TWOPI, false);
  2065. co.fill();
  2066. });
  2067. }
  2068. };
  2069. /**
  2070. * Using a function to add events makes it easier to facilitate method chaining
  2071. *
  2072. * @param string type The type of even to add
  2073. * @param function func
  2074. */
  2075. this.on = function (type, func)
  2076. {
  2077. if (type.substr(0,2) !== 'on') {
  2078. type = 'on' + type;
  2079. }
  2080. this[type] = func;
  2081. return this;
  2082. };
  2083. /**
  2084. * This function runs once only
  2085. * (put at the end of the file (before any effects))
  2086. */
  2087. this.firstDrawFunc = function ()
  2088. {
  2089. };
  2090. /**
  2091. * Trace
  2092. *
  2093. * This effect is for the Scatter chart, uses the jQuery library and slowly
  2094. * uncovers the Line/marks, but you can see the background of the chart.
  2095. */
  2096. this.trace = function ()
  2097. {
  2098. var callback = typeof(arguments[1]) === 'function' ? arguments[1] : function () {};
  2099. var opt = arguments[0] || [];
  2100. var obj = this;
  2101. opt['duration'] = opt['duration'] || 1500;
  2102. if (opt['frames']) {
  2103. opt['duration'] = (opt['frames'] / 60) * 1000;
  2104. }
  2105. RGraph.clear(ca);
  2106. RGraph.redrawCanvas(ca);
  2107. /**
  2108. * Create the DIV that the second canvas will sit in
  2109. */
  2110. var div = document.createElement('DIV');
  2111. var xy = RG.getCanvasXY(this.canvas);
  2112. div.id = '__rgraph_trace_animation_' + RGraph.random(0, 4351623) + '__';
  2113. div.style.left = xy[0] + 'px';
  2114. div.style.top = xy[1] + 'px';
  2115. div.style.width = prop['chart.gutter.left'];
  2116. div.style.height = ca.height + 'px';
  2117. div.style.position = 'absolute';
  2118. div.style.overflow = 'hidden';
  2119. document.body.appendChild(div);
  2120. /**
  2121. * Make the second canvas
  2122. */
  2123. var id = '__rgraph_scatter_trace_animation_' + RG.random(0, 99999999) + '__';
  2124. var canvas2 = document.createElement('CANVAS');
  2125. canvas2.width = ca.width;
  2126. canvas2.height = ca.height;
  2127. canvas2.style.position = 'absolute';
  2128. canvas2.style.left = 0;
  2129. canvas2.style.top = 0;
  2130. // This stops the clear effect clearing the canvas - which can happen if you have multiple canvas tags on the page all with
  2131. // dynamic effects that do redrawing
  2132. canvas2.noclear = true;
  2133. canvas2.id = id;
  2134. div.appendChild(canvas2);
  2135. var reposition_canvas2 = function (e)
  2136. {
  2137. var xy = RGraph.getCanvasXY(obj.canvas);
  2138. div.style.left = xy[0] + 'px';
  2139. div.style.top = xy[1] + 'px';
  2140. }
  2141. window.addEventListener('resize', reposition_canvas2, false)
  2142. /**
  2143. * Make a copy of the original Line object
  2144. */
  2145. var obj2 = new RGraph.Scatter(id, RG.array_clone(this.data));
  2146. // Remove the new line from the ObjectRegistry so that it isn't redawn
  2147. RG.ObjectRegistry.Remove(obj2);
  2148. for (i in prop) {
  2149. if (typeof i === 'string') {
  2150. obj2.Set(i, prop[i]);
  2151. }
  2152. }
  2153. obj2.Set('chart.labels', []);
  2154. obj2.Set('chart.background.grid', false);
  2155. obj2.Set('chart.background.barcolor1', 'rgba(0,0,0,0)');
  2156. obj2.Set('chart.background.barcolor2', 'rgba(0,0,0,0)');
  2157. obj2.Set('chart.ylabels', false);
  2158. obj2.Set('chart.noaxes', true);
  2159. obj2.Set('chart.title', '');
  2160. obj2.Set('chart.title.xaxis', '');
  2161. obj2.Set('chart.title.yaxis', '');
  2162. obj2.Set('chart.key', []);
  2163. obj2.Draw();
  2164. /**
  2165. * This effectively hides the line
  2166. */
  2167. this.set('chart.line.visible', false);
  2168. RGraph.clear(ca);
  2169. RGraph.redrawCanvas(ca);
  2170. /**
  2171. * Place a DIV over the canvas to stop interaction with it
  2172. */
  2173. if (!ca.__rgraph_scatter_trace_cover__) {
  2174. var div2 = document.createElement('DIV');
  2175. div2.id = '__rgraph_trace_animation_' + RGraph.random(0, 4351623) + '__';
  2176. div2.style.left = xy[0] + 'px';
  2177. div2.style.top = xy[1] + 'px';
  2178. div2.style.width = ca.width + 'px';
  2179. div2.style.height = ca.height + 'px';
  2180. div2.style.position = 'absolute';
  2181. div2.style.overflow = 'hidden';
  2182. div2.style.backgroundColor = 'rgba(0,0,0,0)';
  2183. div.div2 = div2;
  2184. ca.__rgraph_scatter_trace_cover__ = div2
  2185. document.body.appendChild(div2);
  2186. } else {
  2187. div2 = ca.__rgraph_scatter_trace_cover__;
  2188. }
  2189. /**
  2190. * Animate the DIV that contains the canvas
  2191. */
  2192. jQuery('#' + div.id).animate({
  2193. width: ca.width + 'px'
  2194. }, opt['duration'], function ()
  2195. {
  2196. // Remove the window resize listener
  2197. window.removeEventListener('resize', reposition_canvas2, false);
  2198. div.style.display = 'none';
  2199. div2.style.display = 'none';
  2200. //div.removeChild(canvas2);
  2201. obj.Set('chart.line.visible', true);
  2202. // Revert the colors back to what they were
  2203. obj.Set('chart.colors', RGraph.array_clone(obj2.Get('chart.colors')));
  2204. obj.Set('chart.key', RG.array_clone(obj2.Get('chart.key')));
  2205. RGraph.RedrawCanvas(ca);
  2206. ca.__rgraph_trace_cover__ = null;
  2207. callback(obj);
  2208. });
  2209. return this;
  2210. };
  2211. /**
  2212. * This helps the Gantt reset colors when the reset function is called.
  2213. * It handles going through the data and resetting the colors.
  2214. */
  2215. this.resetColorsToOriginalValues = function ()
  2216. {
  2217. /**
  2218. * Copy the original colors over for single-event-per-line data
  2219. */
  2220. for (var i=0,len=this.original_colors['data'].length; i<len; ++i) {
  2221. for (var j=0,len2=this.original_colors['data'][i].length; j<len2;++j) {
  2222. // The color for the point
  2223. this.data[i][j][2] = RG.array_clone(this.original_colors['data'][i][j][2]);
  2224. // Handle boxplots
  2225. if (typeof this.data[i][j][1] === 'object') {
  2226. this.data[i][j][1][5] = RG.array_clone(this.original_colors['data'][i][j][1][5]);
  2227. this.data[i][j][1][6] = RG.array_clone(this.original_colors['data'][i][j][1][6]);
  2228. }
  2229. }
  2230. }
  2231. };
  2232. /**
  2233. * Register the object
  2234. */
  2235. RG.Register(this);
  2236. };