RGraph.radar.js 57 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538
  1. /**
  2. /**
  3. * o------------------------------------------------------------------------------o
  4. * | This file is part of the RGraph package - you can learn more at: |
  5. * | |
  6. * | http://www.rgraph.net |
  7. * | |
  8. * | This package is licensed under the RGraph license. For all kinds of business |
  9. * | purposes there is a small one-time licensing fee to pay and for non |
  10. * | commercial purposes it is free to use. You can read the full license here: |
  11. * | |
  12. * | http://www.rgraph.net/LICENSE.txt |
  13. * o------------------------------------------------------------------------------o
  14. */
  15. if (typeof(RGraph) == 'undefined') RGraph = {};
  16. /**
  17. * The traditional radar chart constructor
  18. *
  19. * @param string id The ID of the canvas
  20. * @param array data An array of data to represent
  21. */
  22. RGraph.Radar = function (id, data)
  23. {
  24. this.id = id;
  25. this.canvas = document.getElementById(typeof id === 'object' ? id.id : id);
  26. this.context = this.canvas.getContext('2d');
  27. this.canvas.__object__ = this;
  28. this.type = 'radar';
  29. this.coords = [];
  30. this.isRGraph = true;
  31. this.data = [];
  32. this.max = 0;
  33. this.original_data = [];
  34. this.uid = RGraph.CreateUID();
  35. this.canvas.uid = this.canvas.uid ? this.canvas.uid : RGraph.CreateUID();
  36. this.colorsParsed = false;
  37. this.coordsText = [];
  38. /**
  39. * This allows for passing all of the arguments as one big array instead of as individual daatasets
  40. */
  41. if (typeof arguments[1] == 'object' && typeof arguments[1][0] == 'object') {
  42. for (var i=0,len=arguments[1].length; i<len; ++i) {
  43. this.original_data.push(RGraph.array_clone(arguments[1][i]));
  44. this.data.push(RGraph.array_clone(arguments[1][i]));
  45. this.max = Math.max(this.max, RGraph.array_max(arguments[1][i]));
  46. }
  47. } else {
  48. for (var i=1,len=arguments.length; i<len; ++i) {
  49. this.original_data.push(RGraph.array_clone(arguments[i]));
  50. this.data.push(RGraph.array_clone(arguments[i]));
  51. this.max = Math.max(this.max, RGraph.array_max(arguments[i]));
  52. }
  53. }
  54. /**
  55. * Compatibility with older browsers
  56. */
  57. RGraph.OldBrowserCompat(this.context);
  58. this.properties = {
  59. 'chart.strokestyle': '#aaa',
  60. 'chart.gutter.left': 25,
  61. 'chart.gutter.right': 25,
  62. 'chart.gutter.top': 25,
  63. 'chart.gutter.bottom': 25,
  64. 'chart.linewidth': 1,
  65. 'chart.colors': ['rgba(255,255,0,0.25)','rgba(0,255,255,0.25)','rgba(255,0,0,0.5)', 'red', 'green', 'blue', 'pink', 'aqua','brown','orange','grey'],
  66. 'chart.colors.alpha': null,
  67. 'chart.circle': 0,
  68. 'chart.circle.fill': 'red',
  69. 'chart.circle.stroke': 'black',
  70. 'chart.labels': [],
  71. 'chart.labels.offset': 10,
  72. 'chart.background.circles': true,
  73. 'chart.background.circles.count': null,
  74. 'chart.background.circles.color': '#ddd',
  75. 'chart.background.circles.poly': true,
  76. 'chart.text.size': 10,
  77. 'chart.text.size.scale': null,
  78. 'chart.text.font': 'Arial',
  79. 'chart.text.color': 'black',
  80. 'chart.title': '',
  81. 'chart.title.background': null,
  82. 'chart.title.hpos': null,
  83. 'chart.title.vpos': null,
  84. 'chart.title.color': 'black',
  85. 'chart.title.bold': true,
  86. 'chart.title.font': null,
  87. 'chart.title.x': null,
  88. 'chart.title.y': null,
  89. 'chart.title.halign': null,
  90. 'chart.title.valign': null,
  91. 'chart.linewidth': 1,
  92. 'chart.key': null,
  93. 'chart.key.background': 'white',
  94. 'chart.key.shadow': false,
  95. 'chart.key.shadow.color': '#666',
  96. 'chart.key.shadow.blur': 3,
  97. 'chart.key.shadow.offsetx': 2,
  98. 'chart.key.shadow.offsety': 2,
  99. 'chart.key.position': 'graph',
  100. 'chart.key.halign': 'right',
  101. 'chart.key.position.gutter.boxed': false,
  102. 'chart.key.position.x': null,
  103. 'chart.key.position.y': null,
  104. 'chart.key.color.shape': 'square',
  105. 'chart.key.rounded': true,
  106. 'chart.key.linewidth': 1,
  107. 'chart.key.colors': null,
  108. 'chart.key.interactive': false,
  109. 'chart.key.interactive.highlight.chart.stroke': 'rgba(255,0,0,0.3)',
  110. 'chart.key.interactive.highlight.label': 'rgba(255,0,0,0.2)',
  111. 'chart.key.text.color': 'black',
  112. 'chart.contextmenu': null,
  113. 'chart.annotatable': false,
  114. 'chart.annotate.color': 'black',
  115. 'chart.zoom.factor': 1.5,
  116. 'chart.zoom.fade.in': true,
  117. 'chart.zoom.fade.out': true,
  118. 'chart.zoom.hdir': 'right',
  119. 'chart.zoom.vdir': 'down',
  120. 'chart.zoom.frames': 25,
  121. 'chart.zoom.delay': 16.666,
  122. 'chart.zoom.shadow': true,
  123. 'chart.zoom.background': true,
  124. 'chart.zoom.action': 'zoom',
  125. 'chart.tooltips.effect': 'fade',
  126. 'chart.tooltips.event': 'onmousemove',
  127. 'chart.tooltips.css.class': 'RGraph_tooltip',
  128. 'chart.tooltips.highlight': true,
  129. 'chart.highlight.stroke': 'gray',
  130. 'chart.highlight.fill': 'rgba(255,255,255,0.7)',
  131. 'chart.highlight.point.radius': 2,
  132. 'chart.resizable': false,
  133. 'chart.resize.handle.adjust': [0,0],
  134. 'chart.resize.handle.background': null,
  135. 'chart.labels.axes': '',
  136. 'chart.labels.background.fill': 'white',
  137. 'chart.labels.boxed': false,
  138. 'chart.labels.axes.bold': [],
  139. 'chart.labels.axes.boxed': null, // This defaults to true - but that's set in the Draw() method
  140. 'chart.labels.axes.boxed.zero': true,
  141. 'chart.labels.specific': [],
  142. 'chart.labels.count': 5,
  143. 'chart.ymax': null,
  144. 'chart.accumulative': false,
  145. 'chart.radius': null,
  146. 'chart.events.click': null,
  147. 'chart.events.mousemove': null,
  148. 'chart.scale.decimals': 0,
  149. 'chart.scale.point': '.',
  150. 'chart.scale.thousand': ',',
  151. 'chart.units.pre': '',
  152. 'chart.units.post': '',
  153. 'chart.tooltips': null,
  154. 'chart.tooltips.event': 'onmousemove',
  155. 'chart.centerx': null,
  156. 'chart.centery': null,
  157. 'chart.radius': null,
  158. 'chart.numxticks': 5,
  159. 'chart.numyticks': 5,
  160. 'chart.axes.color': 'rgba(0,0,0,0)',
  161. 'chart.highlights': false,
  162. 'chart.highlights.stroke': '#ddd',
  163. 'chart.highlights.fill': null,
  164. 'chart.highlights.radius': 3,
  165. 'chart.fill.click': null,
  166. 'chart.fill.mousemove': null,
  167. 'chart.fill.tooltips': null,
  168. 'chart.fill.highlight.fill': 'rgba(255,255,255,0.7)',
  169. 'chart.fill.highlight.stroke': 'rgba(0,0,0,0)',
  170. 'chart.fill.mousemove.redraw': false,
  171. 'chart.animation.trace.clip': 1
  172. }
  173. // Must have at least 3 points
  174. for (var dataset=0; dataset<this.data.length; ++dataset) {
  175. if (this.data[dataset].length < 3) {
  176. alert('[RADAR] You must specify at least 3 data points');
  177. return;
  178. }
  179. }
  180. /**
  181. * Linearize the data and then create the $ objects
  182. */
  183. var idx = 0;
  184. for (var dataset=0; dataset<this.data.length; ++dataset) {
  185. for (var i=0,len=this.data[dataset].length; i<len; ++i) {
  186. this['$' + (idx++)] = {};
  187. }
  188. }
  189. /**
  190. * Translate half a pixel for antialiasing purposes - but only if it hasn't beeen
  191. * done already
  192. */
  193. if (!this.canvas.__rgraph_aa_translated__) {
  194. this.context.translate(0.5,0.5);
  195. this.canvas.__rgraph_aa_translated__ = true;
  196. }
  197. ///////////////////////////////// SHORT PROPERTIES /////////////////////////////////
  198. var RG = RGraph;
  199. var ca = this.canvas;
  200. var co = ca.getContext('2d');
  201. var prop = this.properties;
  202. //////////////////////////////////// METHODS ///////////////////////////////////////
  203. /**
  204. * A simple setter
  205. *
  206. * @param string name The name of the property to set
  207. * @param string value The value of the property
  208. */
  209. this.Set = function (name, value)
  210. {
  211. name = name.toLowerCase();
  212. /**
  213. * This should be done first - prepend the propertyy name with "chart." if necessary
  214. */
  215. if (name.substr(0,6) != 'chart.') {
  216. name = 'chart.' + name;
  217. }
  218. if (name == 'chart.text.diameter') {
  219. name = 'chart.text.size';
  220. }
  221. /**
  222. * If the name is chart.color, set chart.colors too
  223. */
  224. if (name == 'chart.color') {
  225. this.properties['chart.colors'] = [value];
  226. }
  227. prop[name] = value;
  228. return this;
  229. }
  230. /**
  231. * A simple hetter
  232. *
  233. * @param string name The name of the property to get
  234. */
  235. this.Get = function (name)
  236. {
  237. /**
  238. * This should be done first - prepend the property name with "chart." if necessary
  239. */
  240. if (name.substr(0,6) != 'chart.') {
  241. name = 'chart.' + name;
  242. }
  243. if (name == 'chart.text.diameter') {
  244. name = 'chart.text.size';
  245. }
  246. return prop[name];
  247. }
  248. /**
  249. * The draw method which does all the brunt of the work
  250. */
  251. this.Draw = function ()
  252. {
  253. /**
  254. * Fire the onbeforedraw event
  255. */
  256. RG.FireCustomEvent(this, 'onbeforedraw');
  257. // NB: Colors are parsed further down
  258. // Reset the coords array to stop it growing
  259. this.coords = [];
  260. this.coords2 = [];
  261. /**
  262. * Reset the data to the original_data
  263. */
  264. this.data = RG.array_clone(this.original_data);
  265. // Loop thru the data array if chart.accumulative is enable checking to see if all the
  266. // datasets have the same number of elements.
  267. if (prop['chart.accumulative']) {
  268. for (var i=0; i<this.data.length; ++i) {
  269. if (this.data[i].length != this.data[0].length) {
  270. alert('[RADAR] Error! When the radar has chart.accumulative set to true all the datasets must have the same number of elements');
  271. }
  272. }
  273. }
  274. /**
  275. * This defaults to true, but needs to be an array with a size matching the number of
  276. * labels.
  277. */
  278. if (RG.is_null(prop['chart.labels.axes.boxed'])) {
  279. prop['chart.labels.axes.boxed'] = [];
  280. for (var i=0; i<(prop['chart.labels.specific'].length || prop['chart.labels.count'] || 5); ++i) {
  281. prop['chart.labels.axes.boxed'][i] = true;
  282. }
  283. }
  284. /**
  285. * This is new in May 2011 and facilitates indiviual gutter settings,
  286. * eg chart.gutter.left
  287. */
  288. this.gutterLeft = prop['chart.gutter.left'];
  289. this.gutterRight = prop['chart.gutter.right'];
  290. this.gutterTop = prop['chart.gutter.top'];
  291. this.gutterBottom = prop['chart.gutter.bottom'];
  292. this.centerx = ((ca.width - this.gutterLeft - this.gutterRight) / 2) + this.gutterLeft;
  293. this.centery = ((ca.height - this.gutterTop - this.gutterBottom) / 2) + this.gutterTop;
  294. this.radius = Math.min(ca.width - this.gutterLeft - this.gutterRight, ca.height - this.gutterTop - this.gutterBottom) / 2;
  295. /**
  296. * Allow these to be set by hand
  297. */
  298. if (typeof(prop['chart.centerx']) == 'number') this.centerx = 2 * prop['chart.centerx'];
  299. if (typeof(prop['chart.centery']) == 'number') this.centery = 2 * prop['chart.centery'];
  300. if (typeof(prop['chart.radius']) == 'number') this.radius = prop['chart.radius'];
  301. /**
  302. * Parse the colors for gradients. Its down here so that the center X/Y can be used
  303. */
  304. if (!this.colorsParsed) {
  305. this.parseColors();
  306. // Don't want to do this again
  307. this.colorsParsed = true;
  308. }
  309. // Work out the maximum value and the sum
  310. if (!prop['chart.ymax']) {
  311. // this.max is calculated in the constructor
  312. // Work out this.max again if the chart is (now) set to be accumulative
  313. if (prop['chart.accumulative']) {
  314. var accumulation = [];
  315. var len = this.original_data[0].length
  316. for (var i=1; i<this.original_data.length; ++i) {
  317. if (this.original_data[i].length != len) {
  318. alert('[RADAR] Error! Stacked Radar chart datasets must all be the same size!');
  319. }
  320. for (var j=0; j<this.original_data[i].length; ++j) {
  321. this.data[i][j] += this.data[i - 1][j];
  322. this.max = Math.max(this.max, this.data[i][j]);
  323. }
  324. }
  325. }
  326. this.scale2 = RG.getScale2(this, {'max':typeof(prop['chart.ymax']) == 'number' ? prop['chart.ymax'] : this.max,
  327. 'min':0,
  328. 'scale.decimals':Number(prop['chart.scale.decimals']),
  329. 'scale.point':prop['chart.scale.point'],
  330. 'scale.thousand':prop['chart.scale.thousand'],
  331. 'scale.round':prop['chart.scale.round'],
  332. 'units.pre':prop['chart.units.pre'],
  333. 'units.post':prop['chart.units.post'],
  334. 'ylabels.count':prop['chart.labels.count']
  335. });
  336. this.max = this.scale2.max;
  337. } else {
  338. var ymax = prop['chart.ymax'];
  339. this.scale2 = RG.getScale2(this, {'max':ymax,
  340. 'min':0,
  341. 'strict':true,
  342. 'scale.decimals':Number(prop['chart.scale.decimals']),
  343. 'scale.point':prop['chart.scale.point'],
  344. 'scale.thousand':prop['chart.scale.thousand'],
  345. 'scale.round':prop['chart.scale.round'],
  346. 'units.pre':prop['chart.units.pre'],
  347. 'units.post':prop['chart.units.post'],
  348. 'ylabels.count':prop['chart.labels.count']
  349. });
  350. this.max = this.scale2.max;
  351. }
  352. this.DrawBackground();
  353. this.DrawAxes();
  354. this.DrawCircle();
  355. this.DrawLabels();
  356. this.DrawAxisLabels();
  357. /**
  358. * Allow clipping
  359. */
  360. co.save();
  361. co.beginPath();
  362. co.arc(this.centerx, this.centery, this.radius * 2, -HALFPI, (TWOPI * prop['chart.animation.trace.clip']) - HALFPI, false);
  363. co.lineTo(this.centerx, this.centery);
  364. co.closePath();
  365. co.clip();
  366. this.DrawChart();
  367. this.DrawHighlights();
  368. co.restore();
  369. // Draw the title
  370. if (prop['chart.title']) {
  371. RG.DrawTitle(this, prop['chart.title'], this.gutterTop, null, prop['chart.title.diameter'] ? prop['chart.title.diameter'] : null)
  372. }
  373. // Draw the key if necessary
  374. // obj, key, colors
  375. if (prop['chart.key']) {
  376. RG.DrawKey(this, prop['chart.key'], prop['chart.colors']);
  377. }
  378. /**
  379. * Show the context menu
  380. */
  381. if (prop['chart.contextmenu']) {
  382. RG.ShowContext(this);
  383. }
  384. /**
  385. * This function enables resizing
  386. */
  387. if (prop['chart.resizable']) {
  388. RG.AllowResizing(this);
  389. }
  390. /**
  391. * This installs the event listeners
  392. */
  393. RG.InstallEventListeners(this);
  394. /**
  395. * This installs the Radar chart specific area listener
  396. */
  397. if ( (prop['chart.fill.click'] || prop['chart.fill.mousemove'] || !RG.is_null(prop['chart.fill.tooltips'])) && !this.__fill_click_listeners_installed__) {
  398. this.AddFillListeners();
  399. this.__fill_click_listeners_installed__ = true;
  400. }
  401. /**
  402. * Fire the RGraph ondraw event
  403. */
  404. RGraph.FireCustomEvent(this, 'ondraw');
  405. return this;
  406. }
  407. /**
  408. * Draws the background circles
  409. */
  410. this.DrawBackground = function ()
  411. {
  412. var color = prop['chart.background.circles.color'];
  413. var poly = prop['chart.background.circles.poly'];
  414. var spacing = prop['chart.background.circles.spacing'];
  415. // Set the linewidth for the grid (so that repeated redrawing works OK)
  416. co.lineWidth = 1;
  417. /**
  418. * Draws the background circles
  419. */
  420. if (prop['chart.background.circles'] && poly == false) {
  421. // Draw the concentric circles
  422. co.strokeStyle = color;
  423. co.beginPath();
  424. var numrings = typeof(prop['chart.background.circles.count']) == 'number' ? prop['chart.background.circles.count'] : prop['chart.labels.count'];
  425. // TODO Currently set to 5 - needs changing
  426. for (var r=0; r<=this.radius; r+=(this.radius / numrings)) {
  427. co.moveTo(this.centerx, this.centery);
  428. co.arc(this.centerx, this.centery,r, 0, TWOPI, false);
  429. }
  430. co.stroke();
  431. /**
  432. * Draw the diagonals/spokes
  433. */
  434. co.strokeStyle = color;
  435. for (var i=0; i<360; i+=15) {
  436. co.beginPath();
  437. co.arc(this.centerx,
  438. this.centery,
  439. this.radius,
  440. (i / 360) * TWOPI,
  441. ((i+0.001) / 360) * TWOPI,
  442. false); // The 0.01 avoids a bug in Chrome 6
  443. co.lineTo(this.centerx, this.centery);
  444. co.stroke();
  445. }
  446. /**
  447. * The background"circles" are actually drawn as a poly based on how many points there are
  448. * (ie hexagons if there are 6 points, squares if the are four etc)
  449. */
  450. } else if (prop['chart.background.circles'] && poly == true) {
  451. /**
  452. * Draw the diagonals/spokes
  453. */
  454. co.strokeStyle = color;
  455. var increment = 360 / this.data[0].length
  456. for (var i=0; i<360; i+=increment) {
  457. co.beginPath();
  458. co.arc(this.centerx,
  459. this.centery,
  460. this.radius,
  461. ((i / 360) * TWOPI) - HALFPI,
  462. (((i + 0.001) / 360) * TWOPI) - HALFPI,
  463. false); // The 0.001 avoids a bug in Chrome 6
  464. co.lineTo(this.centerx, this.centery);
  465. co.stroke();
  466. }
  467. /**
  468. * Draw the lines that go around the Radar chart
  469. */
  470. co.strokeStyle = color;
  471. var numrings = typeof(prop['chart.background.circles.count']) == 'number' ? prop['chart.background.circles.count'] : prop['chart.labels.count'];
  472. for (var r=0; r<=this.radius; r+=(this.radius / numrings)) {
  473. co.beginPath();
  474. for (var a=0; a<=360; a+=(360 / this.data[0].length)) {
  475. co.arc(this.centerx,
  476. this.centery,
  477. r,
  478. RG.degrees2Radians(a) - HALFPI,
  479. RG.degrees2Radians(a) + 0.001 - HALFPI,
  480. false);
  481. }
  482. co.closePath();
  483. co.stroke();
  484. }
  485. }
  486. }
  487. /**
  488. * Draws the axes
  489. */
  490. this.DrawAxes = function ()
  491. {
  492. co.strokeStyle = prop['chart.axes.color'];
  493. var halfsize = this.radius;
  494. co.beginPath();
  495. /**
  496. * The Y axis
  497. */
  498. co.moveTo(Math.round(this.centerx), this.centery + this.radius);
  499. co.lineTo(Math.round(this.centerx), this.centery - this.radius);
  500. // Draw the bits at either end of the Y axis
  501. co.moveTo(this.centerx - 5, Math.round(this.centery + this.radius));
  502. co.lineTo(this.centerx + 5, Math.round(this.centery + this.radius));
  503. co.moveTo(this.centerx - 5, Math.round(this.centery - this.radius));
  504. co.lineTo(this.centerx + 5, Math.round(this.centery - this.radius));
  505. // Draw Y axis tick marks
  506. for (var y=(this.centery - this.radius); y<(this.centery + this.radius); y+=(this.radius/prop['chart.numyticks'])) {
  507. co.moveTo(this.centerx - 3, Math.round(y));
  508. co.lineTo(this.centerx + 3, Math.round(y));
  509. }
  510. /**
  511. * The X axis
  512. */
  513. co.moveTo(this.centerx - this.radius, Math.round(this.centery));
  514. co.lineTo(this.centerx + this.radius, Math.round(this.centery));
  515. // Draw the bits at the end of the X axis
  516. co.moveTo(Math.round(this.centerx - this.radius), this.centery - 5);
  517. co.lineTo(Math.round(this.centerx - this.radius), this.centery + 5);
  518. co.moveTo(Math.round(this.centerx + this.radius), this.centery - 5);
  519. co.lineTo(Math.round(this.centerx + this.radius), this.centery + 5);
  520. // Draw X axis tick marks
  521. for (var x=(this.centerx - this.radius); x<(this.centerx + this.radius); x+=(this.radius/prop['chart.numxticks'])) {
  522. co.moveTo(Math.round(x), this.centery - 3);
  523. co.lineTo(Math.round(x), this.centery + 3);
  524. }
  525. // Stroke it
  526. co.stroke();
  527. }
  528. /**
  529. * The function which actually draws the radar chart
  530. */
  531. this.DrawChart = function ()
  532. {
  533. var alpha = prop['chart.colors.alpha'];
  534. if (typeof(alpha) == 'number') {
  535. var oldAlpha = co.globalAlpha;
  536. co.globalAlpha = alpha;
  537. }
  538. var numDatasets = this.data.length;
  539. for (var dataset=0; dataset<this.data.length; ++dataset) {
  540. co.beginPath();
  541. var coords_dataset = [];
  542. for (var i=0; i<this.data[dataset].length; ++i) {
  543. var coords = this.GetCoordinates(dataset, i);
  544. if (coords_dataset == null) {
  545. coords_dataset = [];
  546. }
  547. coords_dataset.push(coords);
  548. this.coords.push(coords);
  549. }
  550. this.coords2[dataset] = coords_dataset;
  551. /**
  552. * Now go through the coords and draw the chart itself
  553. *
  554. * 18/5/2012 - chart.strokestyle can now be an array of colors as well as a single color
  555. */
  556. co.strokeStyle = (typeof(prop['chart.strokestyle']) == 'object' && prop['chart.strokestyle'][dataset]) ? prop['chart.strokestyle'][dataset] : prop['chart.strokestyle'];
  557. co.fillStyle = prop['chart.colors'][dataset];
  558. co.lineWidth = prop['chart.linewidth'];
  559. for (i=0; i<coords_dataset.length; ++i) {
  560. if (i == 0) {
  561. co.moveTo(coords_dataset[i][0], coords_dataset[i][1]);
  562. } else {
  563. co.lineTo(coords_dataset[i][0], coords_dataset[i][1]);
  564. }
  565. }
  566. // If on the second or greater dataset, backtrack
  567. if (prop['chart.accumulative'] && dataset > 0) {
  568. // This goes back to the start coords of this particular dataset
  569. co.lineTo(coords_dataset[0][0], coords_dataset[0][1]);
  570. //Now move down to the end point of the previous dataset
  571. co.moveTo(last_coords[0][0], last_coords[0][1]);
  572. for (var i=coords_dataset.length - 1; i>=0; --i) {
  573. co.lineTo(last_coords[i][0], last_coords[i][1]);
  574. }
  575. }
  576. // This is used by the next iteration of the loop
  577. var last_coords = coords_dataset;
  578. co.closePath();
  579. co.stroke();
  580. co.fill();
  581. }
  582. // Reset the globalAlpha
  583. if (typeof(alpha) == 'number') {
  584. co.globalAlpha = oldAlpha;
  585. }
  586. }
  587. /**
  588. * Gets the coordinates for a particular mark
  589. *
  590. * @param number i The index of the data (ie which one it is)
  591. * @return array A two element array of the coordinates
  592. */
  593. this.GetCoordinates = function (dataset, index)
  594. {
  595. // The number of data points
  596. var len = this.data[dataset].length;
  597. // The magnitude of the data (NOT the x/y coords)
  598. var mag = (this.data[dataset][index] / this.max) * this.radius;
  599. /**
  600. * Get the angle
  601. */
  602. var angle = (TWOPI / len) * index; // In radians
  603. angle -= HALFPI;
  604. /**
  605. * Work out the X/Y coordinates
  606. */
  607. var x = Math.cos(angle) * mag;
  608. var y = Math.sin(angle) * mag;
  609. /**
  610. * Put the coordinate in the right quadrant
  611. */
  612. x = this.centerx + x;
  613. y = this.centery + y;
  614. return [x,y];
  615. }
  616. /**
  617. * This function adds the labels to the chart
  618. */
  619. this.DrawLabels = function ()
  620. {
  621. var labels = prop['chart.labels'];
  622. if (labels && labels.length > 0) {
  623. co.lineWidth = 1;
  624. co.strokeStyle = 'gray';
  625. co.fillStyle = prop['chart.text.color'];
  626. var bgFill = prop['chart.labels.background.fill'];
  627. var bold = prop['chart.labels.bold'];
  628. var bgBoxed = prop['chart.labels.boxed'];
  629. var offset = prop['chart.labels.offset'];
  630. var font = prop['chart.text.font'];
  631. var size = prop['chart.text.size'];
  632. var radius = this.radius;
  633. for (var i=0; i<labels.length; ++i) {
  634. var angle = (TWOPI / prop['chart.labels'].length) * i;
  635. angle -= HALFPI;
  636. var x = this.centerx + (Math.cos(angle) * (radius + offset));
  637. var y = this.centery + (Math.sin(angle) * (radius + offset));
  638. /**
  639. * Horizontal alignment
  640. */
  641. var halign = x < this.centerx ? 'right' : 'left' ;
  642. if (i == 0 || (i / labels.length) == 0.5) halign = 'center';
  643. if (labels[i] && labels[i].length) {
  644. RG.Text2(this, {'font':font,
  645. 'size':size,
  646. 'x':x,
  647. 'y':y,
  648. 'text':labels[i],
  649. 'valign':'center',
  650. 'halign':halign,
  651. 'bounding':bgBoxed,
  652. 'boundingFill':bgFill,
  653. 'bold':bold,
  654. 'tag': 'labels'
  655. });
  656. }
  657. }
  658. }
  659. }
  660. /**
  661. * Draws the circle. No arguments as it gets the information from the object properties.
  662. */
  663. this.DrawCircle = function ()
  664. {
  665. var circle = {};
  666. circle.limit = prop['chart.circle'];
  667. circle.fill = prop['chart.circle.fill'];
  668. circle.stroke = prop['chart.circle.stroke'];
  669. if (circle.limit) {
  670. var r = (circle.limit / this.max) * this.radius;
  671. co.fillStyle = circle.fill;
  672. co.strokeStyle = circle.stroke;
  673. co.beginPath();
  674. co.arc(this.centerx, this.centery, r, 0, TWOPI, 0);
  675. co.fill();
  676. co.stroke();
  677. }
  678. }
  679. /**
  680. * Unsuprisingly, draws the labels
  681. */
  682. this.DrawAxisLabels = function ()
  683. {
  684. /**
  685. * Draw specific axis labels
  686. */
  687. if (RG.is_array(prop['chart.labels.specific']) && prop['chart.labels.specific'].length) {
  688. this.DrawSpecificAxisLabels();
  689. return;
  690. }
  691. co.lineWidth = 1;
  692. // Set the color to black
  693. co.fillStyle = 'black';
  694. co.strokeStyle = 'black';
  695. var r = this.radius;
  696. var font = prop['chart.text.font'];
  697. var size = typeof(prop['chart.text.size.scale']) == 'number' ? prop['chart.text.size.scale'] : prop['chart.text.size'];
  698. var axes = prop['chart.labels.axes'].toLowerCase();
  699. var color = 'white';
  700. var drawzero = false;
  701. var units_pre = prop['chart.units.pre'];
  702. var units_post = prop['chart.units.post'];
  703. var decimals = prop['chart.scale.decimals'];
  704. var bold = prop['chart.labels.axes.bold'];
  705. var boxed = prop['chart.labels.axes.boxed'];
  706. var centerx = this.centerx;
  707. var centery = this.centery;
  708. var scale = this.scale;
  709. co.fillStyle = prop['chart.text.color'];
  710. // The "North" axis labels
  711. if (axes.indexOf('n') > -1) {
  712. for (var i=0; i<this.scale2.labels.length; ++i) {
  713. RG.Text2(this, {'bold':bold[i],
  714. 'font':font,
  715. 'size':size,
  716. 'x':centerx,
  717. 'y':centery - (r * ((i+1)/this.scale2.labels.length)),
  718. 'text':this.scale2.labels[i],
  719. 'valign':'center',
  720. 'halign':'center',
  721. 'bounding':boxed[i],
  722. 'boundingFill':color,
  723. 'tag': 'scale'
  724. });
  725. }
  726. drawzero = true;
  727. }
  728. // The "South" axis labels
  729. if (axes.indexOf('s') > -1) {
  730. for (var i=0; i<this.scale2.labels.length; ++i) {
  731. RG.Text2(this, {'bold':bold[i],
  732. 'font':font,
  733. 'size':size,
  734. 'x':centerx,
  735. 'y':centery + (r * ((i+1)/this.scale2.labels.length)),
  736. 'text':this.scale2.labels[i],
  737. 'valign':'center',
  738. 'halign':'center',
  739. 'bounding':boxed[i],
  740. 'boundingFill':color,
  741. 'tag': 'scale'
  742. });
  743. }
  744. drawzero = true;
  745. }
  746. // The "East" axis labels
  747. if (axes.indexOf('e') > -1) {
  748. for (var i=0; i<this.scale2.labels.length; ++i) {
  749. RG.Text2(this, {'bold':bold[i],
  750. 'font':font,
  751. 'size':size,
  752. 'x':centerx + (r * ((i+1)/this.scale2.labels.length)),
  753. 'y':centery,
  754. 'text':this.scale2.labels[i],
  755. 'valign':'center',
  756. 'halign':'center',
  757. 'bounding':boxed[i],
  758. 'boundingFill':color,
  759. 'tag': 'scale'
  760. });
  761. }
  762. drawzero = true;
  763. }
  764. // The "West" axis labels
  765. if (axes.indexOf('w') > -1) {
  766. for (var i=0; i<this.scale2.labels.length; ++i) {
  767. RG.Text2(this, {'bold':bold[i],
  768. 'font':font,
  769. 'size':size,
  770. 'x':centerx - (r * ((i+1)/this.scale2.labels.length)),
  771. 'y':centery,
  772. 'text':this.scale2.labels[i],
  773. 'valign':'center',
  774. 'halign':'center',
  775. 'bounding':boxed[i],
  776. 'boundingFill':color,
  777. 'tag': 'scale'
  778. });
  779. }
  780. drawzero = true;
  781. }
  782. if (drawzero) {
  783. RG.Text2(this, {'font':font,
  784. 'size':size,
  785. 'x':centerx,
  786. 'y':centery,
  787. 'text':RG.number_format(this, (0).toFixed(decimals), units_pre, units_post),
  788. 'valign':'center',
  789. 'halign':'center',
  790. 'bounding':prop['chart.labels.axes.boxed.zero'],
  791. 'boundingFill':color,
  792. 'bold':prop['chart.labels.axes.bold.zero'],
  793. 'tag': 'scale'
  794. });
  795. }
  796. }
  797. /**
  798. * Draws specific axis labels
  799. */
  800. this.DrawSpecificAxisLabels = function ()
  801. {
  802. /**
  803. * Specific axis labels
  804. */
  805. var labels = prop['chart.labels.specific'];
  806. var bold = RG.array_pad(prop['chart.labels.axes.bold'],labels.length);
  807. var boxed = RG.array_pad(prop['chart.labels.axes.boxed'],labels.length);
  808. var reversed_labels = RG.array_reverse(labels);
  809. var reversed_bold = RG.array_reverse(bold);
  810. var reversed_boxed = RG.array_reverse(boxed);
  811. var font = prop['chart.text.font'];
  812. var size = typeof(prop['chart.text.size.scale']) == 'number' ? prop['chart.text.size.scale'] : prop['chart.text.size'];
  813. var axes = prop['chart.labels.axes'].toLowerCase();
  814. co.fillStyle = prop['chart.text.color'];
  815. for (var i=0; i<labels.length; ++i) {
  816. if (axes.indexOf('n') > -1) RG.Text2(this, {'tag': 'labels.specific', 'bold':reversed_bold[i],'font':font,'size':size,'x':this.centerx,'y':this.centery - this.radius + ((this.radius / labels.length) * i),'text':reversed_labels[i],'valign':'center','halign':'center','bounding':reversed_boxed[i],'boundingFill':'white'});
  817. if (axes.indexOf('s') > -1) RG.Text2(this, {'tag': 'labels.specific', 'bold':bold[i],'font':font,'size':size,'x':this.centerx,'y':this.centery + ((this.radius / labels.length) * (i+1)),'text':labels[i],'valign':'center','halign':'center','bounding':boxed[i],'boundingFill':'white'});
  818. if (axes.indexOf('w') > -1) RG.Text2(this, {'tag': 'labels.specific', 'bold':reversed_bold[i],'font':font,'size':size,'x':this.centerx - this.radius + ((this.radius / labels.length) * i),'y':this.centery,'text':reversed_labels[i],'valign':'center','halign':'center','bounding':reversed_boxed[i],'boundingFill':'white'});
  819. if (axes.indexOf('e') > -1) RG.Text2(this, {'tag': 'labels.specific', 'bold':bold[i],'font':font,'size':size,'x':this.centerx + ((this.radius / labels.length) * (i+1)),'y':this.centery,'text':labels[i],'valign':'center','halign':'center','bounding':boxed[i],'boundingFill':'white'});
  820. }
  821. }
  822. /**
  823. * This method eases getting the focussed point (if any)
  824. *
  825. * @param event e The event object
  826. */
  827. this.getShape =
  828. this.getPoint = function (e)
  829. {
  830. for (var i=0; i<this.coords.length; ++i) {
  831. var x = this.coords[i][0];
  832. var y = this.coords[i][1];
  833. var tooltips = prop['chart.tooltips'];
  834. var index = Number(i);
  835. var mouseXY = RG.getMouseXY(e);
  836. var mouseX = mouseXY[0];
  837. var mouseY = mouseXY[1];
  838. if ( mouseX < (x + 5)
  839. && mouseX > (x - 5)
  840. && mouseY > (y - 5)
  841. && mouseY < (y + 5)
  842. ) {
  843. var tooltip = RG.parseTooltipText(prop['chart.tooltips'], index);
  844. return {0: this, 'object': this,
  845. 1: x, 'x': x,
  846. 2: y, 'y': y,
  847. 3: null, 'dataset': null,
  848. 4: index, 'index': i,
  849. 'tooltip': tooltip
  850. }
  851. }
  852. }
  853. }
  854. /**
  855. * Each object type has its own Highlight() function which highlights the appropriate shape
  856. *
  857. * @param object shape The shape to highlight
  858. */
  859. this.Highlight = function (shape)
  860. {
  861. // Add the new highlight
  862. RG.Highlight.Point(this, shape);
  863. }
  864. /**
  865. * The getObjectByXY() worker method. Don't call this call:
  866. *
  867. * RGraph.ObjectRegistry.getObjectByXY(e)
  868. *
  869. * @param object e The event object
  870. */
  871. this.getObjectByXY = function (e)
  872. {
  873. var mouseXY = RG.getMouseXY(e);
  874. if (
  875. mouseXY[0] > (this.centerx - this.radius)
  876. && mouseXY[0] < (this.centerx + this.radius)
  877. && mouseXY[1] > (this.centery - this.radius)
  878. && mouseXY[1] < (this.centery + this.radius)
  879. ) {
  880. return this;
  881. }
  882. }
  883. /**
  884. * This function positions a tooltip when it is displayed
  885. *
  886. * @param obj object The chart object
  887. * @param int x The X coordinate specified for the tooltip
  888. * @param int y The Y coordinate specified for the tooltip
  889. * @param objec tooltip The tooltips DIV element
  890. */
  891. this.positionTooltip = function (obj, x, y, tooltip, idx)
  892. {
  893. var dataset = tooltip.__dataset__;
  894. var index = tooltip.__index__;
  895. var coordX = this.coords[index][0];
  896. var coordY = this.coords[index][1];
  897. var canvasXY = RG.getCanvasXY(obj.canvas);
  898. var gutterLeft = this.gutterLeft;
  899. var gutterTop = this.gutterTop;
  900. var width = tooltip.offsetWidth;
  901. // Set the top position
  902. tooltip.style.left = 0;
  903. tooltip.style.top = parseInt(tooltip.style.top) - 9 + 'px';
  904. // By default any overflow is hidden
  905. tooltip.style.overflow = '';
  906. // The arrow
  907. var img = new Image();
  908. img.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAAFCAYAAACjKgd3AAAARUlEQVQYV2NkQAN79+797+RkhC4M5+/bd47B2dmZEVkBCgcmgcsgbAaA9GA1BCSBbhAuA/AagmwQPgMIGgIzCD0M0AMMAEFVIAa6UQgcAAAAAElFTkSuQmCC';
  909. img.style.position = 'absolute';
  910. img.id = '__rgraph_tooltip_pointer__';
  911. img.style.top = (tooltip.offsetHeight - 2) + 'px';
  912. tooltip.appendChild(img);
  913. // Reposition the tooltip if at the edges:
  914. // LEFT edge
  915. if ((canvasXY[0] + coordX - (width / 2)) < 10) {
  916. tooltip.style.left = (canvasXY[0] + coordX - (width * 0.1)) + 'px';
  917. img.style.left = ((width * 0.1) - 8.5) + 'px';
  918. // RIGHT edge
  919. } else if ((canvasXY[0] + coordX + (width / 2)) > document.body.offsetWidth) {
  920. tooltip.style.left = canvasXY[0] + coordX - (width * 0.9) + 'px';
  921. img.style.left = ((width * 0.9) - 8.5) + 'px';
  922. // Default positioning - CENTERED
  923. } else {
  924. tooltip.style.left = (canvasXY[0] + coordX - (width * 0.5)) + 'px';
  925. img.style.left = ((width * 0.5) - 8.5) + 'px';
  926. }
  927. }
  928. /**
  929. * This draws highlights on the points
  930. */
  931. this.DrawHighlights = function ()
  932. {
  933. if (prop['chart.highlights']) {
  934. var sequentialIdx = 0;
  935. var dataset = 0;
  936. var index = 0;
  937. var radius = prop['chart.highlights.radius'];
  938. for (var dataset=0; dataset <this.data.length; ++dataset) {
  939. for (var index=0; index<this.data[dataset].length; ++index) {
  940. co.beginPath();
  941. co.strokeStyle = prop['chart.highlights.stroke'];
  942. co.fillStyle = prop['chart.highlights.fill'] ? prop['chart.highlights.fill'] : ((typeof(prop['chart.strokestyle']) == 'object' && prop['chart.strokestyle'][dataset]) ? prop['chart.strokestyle'][dataset] : prop['chart.strokestyle']);
  943. co.arc(this.coords[sequentialIdx][0], this.coords[sequentialIdx][1], radius, 0, TWOPI, false);
  944. co.stroke();
  945. co.fill();
  946. ++sequentialIdx;
  947. }
  948. }
  949. }
  950. }
  951. /**
  952. * This function returns the radius (ie the distance from the center) for a particular
  953. * value. Note that if you want the angle for a point you can use getAngle(index)
  954. *
  955. * @param number value The value you want the radius for
  956. */
  957. this.getRadius = function (value)
  958. {
  959. if (value < 0 || value > this.max) {
  960. return null;
  961. }
  962. // Radar doesn't support minimum value
  963. var radius = (value / this.max) * this.radius;
  964. return radius;
  965. }
  966. /**
  967. * This function returns the angle (in radians) for a particular index.
  968. *
  969. * @param number numitems The total number of items
  970. * @param number index The zero index number of the item to get the angle for
  971. */
  972. this.getAngle = function (numitems, index)
  973. {
  974. var angle = (TWOPI / numitems) * index;
  975. angle -= HALFPI;
  976. return angle;
  977. }
  978. /**
  979. * This allows for easy specification of gradients
  980. */
  981. this.parseColors = function ()
  982. {
  983. for (var i=0; i<prop['chart.colors'].length; ++i) {
  984. prop['chart.colors'][i] = this.parseSingleColorForGradient(prop['chart.colors'][i]);
  985. }
  986. var keyColors = prop['chart.key.colors'];
  987. if (typeof(keyColors) != 'null' && keyColors && keyColors.length) {
  988. for (var i=0; i<prop['chart.key.colors'].length; ++i) {
  989. prop['chart.key.colors'][i] = this.parseSingleColorForGradient(prop['chart.key.colors'][i]);
  990. }
  991. }
  992. prop['chart.title.color'] = this.parseSingleColorForGradient(prop['chart.title.color']);
  993. prop['chart.text.color'] = this.parseSingleColorForGradient(prop['chart.text.color']);
  994. prop['chart.highlight.stroke'] = this.parseSingleColorForGradient(prop['chart.highlight.stroke']);
  995. prop['chart.highlight.fill'] = this.parseSingleColorForGradient(prop['chart.highlight.fill']);
  996. prop['chart.circle.fill'] = this.parseSingleColorForGradient(prop['chart.circle.fill']);
  997. prop['chart.circle.stroke'] = this.parseSingleColorForGradient(prop['chart.circle.stroke']);
  998. }
  999. /**
  1000. * This parses a single color value
  1001. */
  1002. this.parseSingleColorForGradient = function (color)
  1003. {
  1004. if (!color || typeof(color) != 'string') {
  1005. return color;
  1006. }
  1007. if (color.match(/^gradient\((.*)\)$/i)) {
  1008. var parts = RegExp.$1.split(':');
  1009. // Create the gradient
  1010. var grad = co.createRadialGradient(this.centerx, this.centery, 0, this.centerx, this.centery, this.radius);
  1011. var diff = 1 / (parts.length - 1);
  1012. grad.addColorStop(0, RG.trim(parts[0]));
  1013. for (var j=1; j<parts.length; ++j) {
  1014. grad.addColorStop(j * diff, RG.trim(parts[j]));
  1015. }
  1016. }
  1017. return grad ? grad : color;
  1018. }
  1019. this.AddFillListeners = function (e)
  1020. {
  1021. var obj = this;
  1022. var func = function (e)
  1023. {
  1024. //var canvas = e.target;
  1025. //var context = canvas.getContext('2d');
  1026. var coords = this.coords;
  1027. var coords2 = this.coords2;
  1028. var mouseXY = RG.getMouseXY(e);
  1029. var dataset = 0;
  1030. if (e.type == 'mousemove' && prop['chart.fill.mousemove.redraw']) {
  1031. RG.RedrawCanvas(ca);
  1032. }
  1033. for (var dataset=(obj.coords2.length-1); dataset>=0; --dataset) {
  1034. // Draw the path again so that it can be checked
  1035. co.beginPath();
  1036. co.moveTo(obj.coords2[dataset][0][0], obj.coords2[dataset][0][1]);
  1037. for (var j=0; j<obj.coords2[dataset].length; ++j) {
  1038. co.lineTo(obj.coords2[dataset][j][0], obj.coords2[dataset][j][1]);
  1039. }
  1040. // Draw a line back to the starting point
  1041. co.lineTo(obj.coords2[dataset][0][0], obj.coords2[dataset][0][1]);
  1042. // Go thru the previous datasets coords in reverse order
  1043. if (prop['chart.accumulative'] && dataset > 0) {
  1044. co.lineTo(obj.coords2[dataset - 1][0][0], obj.coords2[dataset - 1][0][1]);
  1045. for (var j=(obj.coords2[dataset - 1].length - 1); j>=0; --j) {
  1046. co.lineTo(obj.coords2[dataset - 1][j][0], obj.coords2[dataset - 1][j][1]);
  1047. }
  1048. }
  1049. co.closePath();
  1050. if (co.isPointInPath(mouseXY[0], mouseXY[1])) {
  1051. var inPath = true;
  1052. break;
  1053. }
  1054. }
  1055. // Call the events
  1056. if (inPath) {
  1057. var fillTooltips = prop['chart.fill.tooltips'];
  1058. /**
  1059. * Click event
  1060. */
  1061. if (e.type == 'click') {
  1062. if (prop['chart.fill.click']) {
  1063. prop['chart.fill.click'](e, dataset);
  1064. }
  1065. if (prop['chart.fill.tooltips'] && prop['chart.fill.tooltips'][dataset]) {
  1066. obj.DatasetTooltip(e, dataset);
  1067. }
  1068. }
  1069. /**
  1070. * Mousemove event
  1071. */
  1072. if (e.type == 'mousemove') {
  1073. if (prop['chart.fill.mousemove']) {
  1074. prop['chart.fill.mousemove'](e, dataset);
  1075. }
  1076. if (!RG.is_null(fillTooltips)) {
  1077. e.target.style.cursor = 'pointer';
  1078. }
  1079. if (prop['chart.fill.tooltips'] && prop['chart.fill.tooltips'][dataset]) {
  1080. e.target.style.cursor = 'pointer';
  1081. }
  1082. }
  1083. e.stopPropagation();
  1084. } else if (e.type == 'mousemove') {
  1085. ca.style.cursor = 'default';
  1086. }
  1087. }
  1088. /**
  1089. * Add the click listener
  1090. */
  1091. if (prop['chart.fill.click'] || !RG.is_null(prop['chart.fill.tooltips'])) {
  1092. ca.addEventListener('click', func, false);
  1093. }
  1094. /**
  1095. * Add the mousemove listener
  1096. */
  1097. if (prop['chart.fill.mousemove'] || !RG.is_null(prop['chart.fill.tooltips'])) {
  1098. ca.addEventListener('mousemove', func, false);
  1099. }
  1100. }
  1101. /**
  1102. * This highlights a specific dataset on the chart
  1103. *
  1104. * @param number dataset The index of the dataset (which starts at zero)
  1105. */
  1106. this.HighlightDataset = function (dataset)
  1107. {
  1108. co.beginPath();
  1109. for (var j=0; j<this.coords2[dataset].length; ++j) {
  1110. if (j == 0) {
  1111. co.moveTo(this.coords2[dataset][0][0], this.coords2[dataset][0][1]);
  1112. } else {
  1113. co.lineTo(this.coords2[dataset][j][0], this.coords2[dataset][j][1]);
  1114. }
  1115. }
  1116. co.lineTo(this.coords2[dataset][0][0], this.coords2[dataset][0][1]);
  1117. if (prop['chart.accumulative'] && dataset > 0) {
  1118. co.lineTo(this.coords2[dataset - 1][0][0], this.coords2[dataset - 1][0][1]);
  1119. for (var j=(this.coords2[dataset - 1].length - 1); j>=0; --j) {
  1120. co.lineTo(this.coords2[dataset - 1][j][0], this.coords2[dataset - 1][j][1]);
  1121. }
  1122. }
  1123. co.strokeStyle = prop['chart.fill.highlight.stroke'];
  1124. co.fillStyle = prop['chart.fill.highlight.fill'];
  1125. co.stroke();
  1126. co.fill();
  1127. }
  1128. /**
  1129. * Shows a tooltip for a dataset (a "fill" tooltip), You can pecify these
  1130. * with chart.fill.tooltips
  1131. */
  1132. this.DatasetTooltip = function (e, dataset)
  1133. {
  1134. // Highlight the dataset
  1135. this.HighlightDataset(dataset);
  1136. // Use the First datapoints coords for the Y position of the tooltip NOTE The X position is changed in the
  1137. // obj.positionTooltip() method so set the index to be the first one
  1138. var text = prop['chart.fill.tooltips'][dataset];
  1139. var x = 0;
  1140. var y = this.coords2[dataset][0][1] + RG.getCanvasXY(ca)[1];
  1141. // Show a tooltip
  1142. RG.Tooltip(this, text, x, y, 0, e);
  1143. }
  1144. /**
  1145. * This function handles highlighting an entire data-series for the interactive
  1146. * key
  1147. *
  1148. * @param int index The index of the data series to be highlighted
  1149. */
  1150. this.interactiveKeyHighlight = function (index)
  1151. {
  1152. var coords = this.coords2[index];
  1153. if (coords) {
  1154. var pre_linewidth = co.lineWidth;
  1155. var pre_linecap = co.lineCap;
  1156. // ------------------------------------------ //
  1157. co.lineWidth = prop['chart.linewidth'] + 10;
  1158. co.lineCap = 'round';
  1159. co.strokeStyle = prop['chart.key.interactive.highlight.chart.stroke'];
  1160. co.beginPath();
  1161. for (var i=0,len=coords.length; i<len; i+=1) {
  1162. if (i == 0) {
  1163. co.moveTo(coords[i][0], coords[i][1]);
  1164. } else {
  1165. co.lineTo(coords[i][0], coords[i][1]);
  1166. }
  1167. }
  1168. co.closePath();
  1169. co.stroke();
  1170. // ------------------------------------------ //
  1171. // Reset the lineCap and lineWidth
  1172. co.lineWidth = pre_linewidth;
  1173. co.lineCap = pre_linecap;
  1174. }
  1175. }
  1176. /**
  1177. * Always register the object
  1178. */
  1179. RG.Register(this);
  1180. }