RGraph.radar.js 62 KB

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