RGraph.rose.js 63 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659
  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. RGraph.Effects = RGraph.Effects || {};
  13. RGraph.Effects.Rose = RGraph.Effects.Rose || {};
  14. /**
  15. * The rose chart constuctor
  16. *
  17. * @param object canvas
  18. * @param array data
  19. */
  20. RGraph.Rose = function (id, data)
  21. {
  22. var tmp = RGraph.getCanvasTag(id);
  23. // Get the canvas and context objects
  24. this.id = tmp[0];
  25. this.canvas = tmp[1];
  26. this.context = this.canvas.getContext ? this.canvas.getContext("2d", {alpha: (typeof id === 'object' && id.alpha === false) ? false : true}) : null;
  27. this.data = data;
  28. this.canvas.__object__ = this;
  29. this.type = 'rose';
  30. this.isRGraph = true;
  31. this.uid = RGraph.CreateUID();
  32. this.canvas.uid = this.canvas.uid ? this.canvas.uid : RGraph.CreateUID();
  33. this.colorsParsed = false;
  34. this.coordsText = [];
  35. this.original_colors = [];
  36. this.firstDraw = true; // After the first draw this will be false
  37. /**
  38. * Compatibility with older browsers
  39. */
  40. //RGraph.OldBrowserCompat(this.context);
  41. this.centerx = 0;
  42. this.centery = 0;
  43. this.radius = 0;
  44. this.max = 0;
  45. this.angles = [];
  46. this.angles2 = [];
  47. this.properties =
  48. {
  49. 'chart.background.axes': true,
  50. 'chart.background.axes.color': 'black',
  51. 'chart.background.grid': true,
  52. 'chart.background.grid.color': '#ccc',
  53. 'chart.background.grid.size': null,
  54. 'chart.background.grid.spokes': null,
  55. 'chart.background.grid.count': 5,
  56. 'chart.centerx': null,
  57. 'chart.centery': null,
  58. 'chart.radius': null,
  59. 'chart.angles.start': 0,
  60. 'chart.colors': ['rgba(255,0,0,0.5)', 'rgba(255,255,0,0.5)', 'rgba(0,255,255,0.5)', 'rgb(0,255,0)', 'gray', 'blue', 'rgb(255,128,255)','green', 'pink', 'gray', 'aqua'],
  61. 'chart.linewidth': 1,
  62. 'chart.colors.sequential': false,
  63. 'chart.colors.alpha': null,
  64. 'chart.margin': 0,
  65. 'chart.strokestyle': '#aaa',
  66. 'chart.gutter.left': 25,
  67. 'chart.gutter.right': 25,
  68. 'chart.gutter.top': 25,
  69. 'chart.gutter.bottom': 25,
  70. 'chart.title': '',
  71. 'chart.title.background': null,
  72. 'chart.title.hpos': null,
  73. 'chart.title.vpos': null,
  74. 'chart.title.bold': true,
  75. 'chart.title.font': null,
  76. 'chart.title.x': null,
  77. 'chart.title.y': null,
  78. 'chart.title.halign': null,
  79. 'chart.title.valign': null,
  80. 'chart.labels': null,
  81. 'chart.labels.position': 'center',
  82. 'chart.labels.axes': 'nsew',
  83. 'chart.labels.offset': 0,
  84. 'chart.text.color': 'black',
  85. 'chart.text.font': 'Arial',
  86. 'chart.text.size': 10,
  87. 'chart.key': null,
  88. 'chart.key.background': 'white',
  89. 'chart.key.position': 'graph',
  90. 'chart.key.halign': 'right',
  91. 'chart.key.shadow': false,
  92. 'chart.key.shadow.color': '#666',
  93. 'chart.key.shadow.blur': 3,
  94. 'chart.key.shadow.offsetx': 2,
  95. 'chart.key.shadow.offsety': 2,
  96. 'chart.key.position.gutter.boxed': false,
  97. 'chart.key.position.x': null,
  98. 'chart.key.position.y': null,
  99. 'chart.key.color.shape': 'square',
  100. 'chart.key.rounded': true,
  101. 'chart.key.linewidth': 1,
  102. 'chart.key.colors': null,
  103. 'chart.key.interactive': false,
  104. 'chart.key.interactive.highlight.chart.stroke': 'black',
  105. 'chart.key.interactive.highlight.chart.fill': 'rgba(255,255,255,0.7)',
  106. 'chart.key.interactive.highlight.label': 'rgba(255,0,0,0.2)',
  107. 'chart.key.text.color': 'black',
  108. 'chart.contextmenu': null,
  109. 'chart.tooltips': null,
  110. 'chart.tooltips.event': 'onclick',
  111. 'chart.tooltips.effect': 'fade',
  112. 'chart.tooltips.css.class': 'RGraph_tooltip',
  113. 'chart.tooltips.highlight': true,
  114. 'chart.highlight.stroke': 'rgba(0,0,0,0)',
  115. 'chart.highlight.fill': 'rgba(255,255,255,0.7)',
  116. 'chart.annotatable': false,
  117. 'chart.annotate.color': 'black',
  118. 'chart.zoom.factor': 1.5,
  119. 'chart.zoom.fade.in': true,
  120. 'chart.zoom.fade.out': true,
  121. 'chart.zoom.hdir': 'right',
  122. 'chart.zoom.vdir': 'down',
  123. 'chart.zoom.frames': 25,
  124. 'chart.zoom.delay': 16.666,
  125. 'chart.zoom.shadow': true,
  126. 'chart.zoom.background': true,
  127. 'chart.zoom.action': 'zoom',
  128. 'chart.resizable': false,
  129. 'chart.resize.handle.adjust': [0,0],
  130. 'chart.resize.handle.background': null,
  131. 'chart.adjustable': false,
  132. 'chart.ymax': null,
  133. 'chart.ymin': 0,
  134. 'chart.scale.decimals': null,
  135. 'chart.scale.point': '.',
  136. 'chart.scale.thousand': ',',
  137. 'chart.variant': 'stacked',
  138. 'chart.exploded': 0,
  139. 'chart.events.mousemove': null,
  140. 'chart.events.click': null,
  141. 'chart.animation.roundrobin.factor': 1,
  142. 'chart.animation.roundrobin.radius': true,
  143. 'chart.animation.grow.multiplier': 1,
  144. 'chart.labels.count': 5
  145. }
  146. /**
  147. * Create the $ objects. In the case of non-equi-angular rose charts it actually creates too many $ objects,
  148. * but it doesn't matter.
  149. */
  150. var linear_data = RGraph.array_linearize(this.data);
  151. for (var i=0; i<linear_data.length; ++i) {
  152. this["$" + i] = {}
  153. }
  154. /**
  155. * Translate half a pixel for antialiasing purposes - but only if it hasn't beeen
  156. * done already
  157. */
  158. if (!this.canvas.__rgraph_aa_translated__) {
  159. this.context.translate(0.5,0.5);
  160. this.canvas.__rgraph_aa_translated__ = true;
  161. }
  162. // Short variable names
  163. var RG = RGraph;
  164. var ca = this.canvas;
  165. var co = ca.getContext('2d');
  166. var prop = this.properties;
  167. var jq = jQuery;
  168. var pa = RG.Path;
  169. var win = window;
  170. var doc = document;
  171. var ma = Math;
  172. /**
  173. * "Decorate" the object with the generic effects if the effects library has been included
  174. */
  175. if (RG.Effects && typeof RG.Effects.decorate === 'function') {
  176. RG.Effects.decorate(this);
  177. }
  178. /**
  179. * A simple setter
  180. *
  181. * @param string name The name of the property to set
  182. * @param string value The value of the property
  183. */
  184. this.set =
  185. this.Set = function (name, value)
  186. {
  187. /**
  188. * This should be done first - prepend the propertyy name with "chart." if necessary
  189. */
  190. if (name.substr(0,6) != 'chart.') {
  191. name = 'chart.' + name;
  192. }
  193. prop[name.toLowerCase()] = value;
  194. return this;
  195. };
  196. /**
  197. * A simple getter
  198. *
  199. * @param string name The name of the property to get
  200. */
  201. this.get =
  202. this.Get = function (name)
  203. {
  204. /**
  205. * This should be done first - prepend the property name with "chart." if necessary
  206. */
  207. if (name.substr(0,6) != 'chart.') {
  208. name = 'chart.' + name;
  209. }
  210. return prop[name.toLowerCase()];
  211. };
  212. /**
  213. * This method draws the rose chart
  214. */
  215. this.draw =
  216. this.Draw = function ()
  217. {
  218. /**
  219. * Fire the onbeforedraw event
  220. */
  221. RG.FireCustomEvent(this, 'onbeforedraw');
  222. /**
  223. * This doesn't affect the chart, but is used for compatibility
  224. */
  225. this.gutterLeft = prop['chart.gutter.left'];
  226. this.gutterRight = prop['chart.gutter.right'];
  227. this.gutterTop = prop['chart.gutter.top'];
  228. this.gutterBottom = prop['chart.gutter.bottom'];
  229. // Calculate the radius
  230. this.radius = (Math.min(ca.width - this.gutterLeft - this.gutterRight, ca.height - this.gutterTop - this.gutterBottom) / 2);
  231. this.centerx = ((ca.width - this.gutterLeft - this.gutterRight) / 2) + this.gutterLeft;
  232. this.centery = ((ca.height - this.gutterTop - this.gutterBottom) / 2) + this.gutterTop;
  233. this.angles = [];
  234. this.angles2 = [];
  235. this.total = 0;
  236. this.startRadians = prop['chart.angles.start'];
  237. this.coordsText = [];
  238. /**
  239. * Change the centerx marginally if the key is defined
  240. */
  241. if (prop['chart.key'] && prop['chart.key'].length > 0 && prop['chart.key'].length >= 3) {
  242. this.centerx = this.centerx - this.gutterRight + 5;
  243. }
  244. // User specified radius, centerx and centery
  245. if (typeof(prop['chart.centerx']) == 'number') this.centerx = prop['chart.centerx'];
  246. if (typeof(prop['chart.centery']) == 'number') this.centery = prop['chart.centery'];
  247. if (typeof(prop['chart.radius']) == 'number') this.radius = prop['chart.radius'];
  248. /**
  249. * Parse the colors for gradients. Its down here so that the center X/Y can be used
  250. */
  251. if (!this.colorsParsed) {
  252. this.parseColors();
  253. // Don't want to do this again
  254. this.colorsParsed = true;
  255. }
  256. this.drawBackground();
  257. this.DrawRose();
  258. this.DrawLabels();
  259. /**
  260. * Set the strokestyle to transparent because of a strange double stroke bug
  261. *
  262. * DO NOT REMOVE
  263. */
  264. co.strokeStyle = 'rgba(0,0,0,0)'
  265. /**
  266. * Setup the context menu if required
  267. */
  268. if (prop['chart.contextmenu']) {
  269. RG.ShowContext(this);
  270. }
  271. /**
  272. * This function enables resizing
  273. */
  274. if (prop['chart.resizable']) {
  275. RG.AllowResizing(this);
  276. }
  277. /**
  278. * This function enables adjusting
  279. */
  280. if (prop['chart.adjustable']) {
  281. RG.AllowAdjusting(this);
  282. }
  283. /**
  284. * This installs the event listeners
  285. */
  286. RG.InstallEventListeners(this);
  287. /**
  288. * Fire the onfirstdraw event
  289. */
  290. if (this.firstDraw) {
  291. RG.fireCustomEvent(this, 'onfirstdraw');
  292. this.firstDrawFunc();
  293. this.firstDraw = false;
  294. }
  295. /**
  296. * Fire the RGraph ondraw event
  297. */
  298. RG.FireCustomEvent(this, 'ondraw');
  299. return this;
  300. };
  301. /**
  302. * This method draws the rose charts background
  303. */
  304. this.drawBackground =
  305. this.DrawBackground = function ()
  306. {
  307. co.lineWidth = 1;
  308. // Draw the background grey circles/spokes
  309. if (prop['chart.background.grid']) {
  310. if (typeof(prop['chart.background.grid.count']) == 'number') {
  311. prop['chart.background.grid.size'] = this.radius / prop['chart.background.grid.count'];
  312. }
  313. co.beginPath();
  314. co.strokeStyle = prop['chart.background.grid.color'];
  315. // Radius must be greater than 0 for Opera to work
  316. for (var i=prop['chart.background.grid.size']; i<=this.radius; i+=prop['chart.background.grid.size']) {
  317. // Hmmm... This is questionable
  318. co.moveTo(this.centerx + i, this.centery);
  319. // Radius must be greater than 0 for Opera to work
  320. co.arc(this.centerx,
  321. this.centery,
  322. i,
  323. 0,
  324. RG.TWOPI,
  325. false);
  326. }
  327. co.stroke();
  328. // Draw the background lines that go from the center outwards
  329. co.beginPath();
  330. if (typeof(prop['chart.background.grid.spokes']) == 'number') {
  331. var num = (360 / prop['chart.background.grid.spokes']);
  332. for (var i=num; i<=360; i+=num) {
  333. // Radius must be greater than 0 for Opera to work
  334. co.arc(this.centerx,
  335. this.centery,
  336. this.radius,
  337. ((i / (180 / RG.PI)) - RG.HALFPI) + this.startRadians,
  338. (((i + 0.0001) / (180 / RG.PI)) - RG.HALFPI) + this.startRadians,
  339. false);
  340. co.lineTo(this.centerx, this.centery);
  341. }
  342. } else {
  343. for (var i=15; i<=360; i+=15) {
  344. // Radius must be greater than 0 for Opera to work
  345. co.arc(this.centerx,
  346. this.centery,
  347. this.radius,
  348. (i / (180/ RG.PI)) - RG.HALFPI,
  349. ((i + 0.0001) / (180/ RG.PI)) - RG.HALFPI,
  350. false);
  351. co.lineTo(this.centerx, this.centery);
  352. }
  353. }
  354. co.stroke();
  355. }
  356. if (prop['chart.background.axes']) {
  357. co.beginPath();
  358. co.strokeStyle = prop['chart.background.axes.color'];
  359. // Draw the X axis
  360. co.moveTo(this.centerx - this.radius, Math.round(this.centery) );
  361. co.lineTo(this.centerx + this.radius, Math.round(this.centery) );
  362. // Draw the X ends
  363. co.moveTo(Math.round(this.centerx - this.radius), this.centery - 5);
  364. co.lineTo(Math.round(this.centerx - this.radius), this.centery + 5);
  365. co.moveTo(Math.round(this.centerx + this.radius), this.centery - 5);
  366. co.lineTo(Math.round(this.centerx + this.radius), this.centery + 5);
  367. // Draw the X check marks
  368. for (var i=(this.centerx - this.radius); i<(this.centerx + this.radius); i+=(this.radius / 5)) {
  369. co.moveTo(Math.round(i), this.centery - 3);
  370. co.lineTo(Math.round(i), this.centery + 3.5);
  371. }
  372. // Draw the Y check marks
  373. for (var i=(this.centery - this.radius); i<(this.centery + this.radius); i+=(this.radius / 5)) {
  374. co.moveTo(this.centerx - 3, Math.round(i));
  375. co.lineTo(this.centerx + 3, Math.round(i));
  376. }
  377. // Draw the Y axis
  378. co.moveTo(Math.round(this.centerx), this.centery - this.radius);
  379. co.lineTo(Math.round(this.centerx), this.centery + this.radius);
  380. // Draw the Y ends
  381. co.moveTo(this.centerx - 5, Math.round(this.centery - this.radius));
  382. co.lineTo(this.centerx + 5, Math.round(this.centery - this.radius));
  383. co.moveTo(this.centerx - 5, Math.round(this.centery + this.radius));
  384. co.lineTo(this.centerx + 5, Math.round(this.centery + this.radius));
  385. // Stroke it
  386. co.closePath();
  387. co.stroke();
  388. }
  389. };
  390. /**
  391. * This method draws the data on the graph
  392. */
  393. this.drawRose =
  394. this.DrawRose = function ()
  395. {
  396. var max = 0;
  397. var data = this.data;
  398. var margin = RGraph.degrees2Radians(prop['chart.margin']);
  399. co.lineWidth = prop['chart.linewidth'];
  400. // Must be at least two data points
  401. //if (data.length < 2) {
  402. // alert('[ROSE] Must be at least two data points! [' + data + ']');
  403. // return;
  404. //}
  405. // Work out the maximum value and the sum
  406. if (RG.is_null(prop['chart.ymax'])) {
  407. // Work out the max
  408. for (var i=0; i<data.length; ++i) {
  409. if (typeof(data[i]) == 'number') {
  410. max = Math.max(max, data[i]);
  411. } else if (typeof(data[i]) == 'object' && prop['chart.variant'] == 'non-equi-angular') {
  412. max = Math.max(max, data[i][0]);
  413. // Fallback is stacked
  414. } else {
  415. max = Math.max(max, RG.array_sum(data[i]));
  416. }
  417. }
  418. this.scale2 = RG.getScale2(this, {
  419. 'max':max,
  420. 'min':0,
  421. 'scale.thousand':prop['chart.scale.thousand'],
  422. 'scale.point':prop['chart.scale.point'],
  423. 'scale.decimals':prop['chart.scale.decimals'],
  424. 'ylabels.count':prop['chart.labels.count'],
  425. 'scale.round':prop['chart.scale.round'],
  426. 'units.pre': prop['chart.units.pre'],
  427. 'units.post': prop['chart.units.post']
  428. });
  429. this.max = this.scale2.max;
  430. } else {
  431. var ymax = prop['chart.ymax'];
  432. this.scale2 = RG.getScale2(this, {
  433. 'max':ymax,
  434. 'strict':true,
  435. 'scale.thousand':prop['chart.scale.thousand'],
  436. 'scale.point':prop['chart.scale.point'],
  437. 'scale.decimals':prop['chart.scale.decimals'],
  438. 'ylabels.count':prop['chart.labels.count'],
  439. 'scale.round':prop['chart.scale.round'],
  440. 'units.pre': prop['chart.units.pre'],
  441. 'units.post': prop['chart.units.post']
  442. });
  443. this.max = this.scale2.max
  444. }
  445. this.sum = RG.array_sum(data);
  446. // Move to the centre
  447. co.moveTo(this.centerx, this.centery);
  448. co.stroke(); // Stroke the background so it stays grey
  449. // Transparency
  450. if (prop['chart.colors.alpha']) {
  451. co.globalAlpha = prop['chart.colors.alpha'];
  452. }
  453. /*******************************************************
  454. * A non-equi-angular Rose chart
  455. *******************************************************/
  456. if (typeof(prop['chart.variant']) == 'string' && prop['chart.variant'] == 'non-equi-angular') {
  457. /*******************************************************
  458. * NON-EQUI-ANGULAR GOES HERE
  459. *******************************************************/
  460. var total=0;
  461. for (var i=0; i<data.length; ++i) {
  462. total += data[i][1];
  463. }
  464. for (var i=0; i<this.data.length; ++i) {
  465. var segmentRadians = ((this.data[i][1] / total) * RG.TWOPI);
  466. var radius = ((this.data[i][0] - prop['chart.ymin']) / (this.max - prop['chart.ymin'])) * this.radius;
  467. radius = radius * prop['chart.animation.grow.multiplier'];
  468. co.strokeStyle = prop['chart.strokestyle'];
  469. co.fillStyle = prop['chart.colors'][0];
  470. if (prop['chart.colors.sequential']) {
  471. co.fillStyle = prop['chart.colors'][i];
  472. }
  473. co.beginPath(); // Begin the segment
  474. var startAngle = (this.startRadians * prop['chart.animation.roundrobin.factor']) - RG.HALFPI + margin;
  475. var endAngle = ((this.startRadians + segmentRadians) * prop['chart.animation.roundrobin.factor']) - RG.HALFPI - margin;
  476. var exploded = this.getexploded(i, startAngle, endAngle, prop['chart.exploded']);
  477. var explodedX = exploded[0];
  478. var explodedY = exploded[1];
  479. co.arc(this.centerx + explodedX,
  480. this.centery + explodedY,
  481. prop['chart.animation.roundrobin.radius'] ? radius * prop['chart.animation.roundrobin.factor'] : radius,
  482. startAngle,
  483. endAngle,
  484. 0);
  485. co.lineTo(this.centerx + explodedX, this.centery + explodedY);
  486. co.closePath(); // End the segment
  487. co.stroke();
  488. co.fill();
  489. // Store the start and end angles
  490. this.angles.push(gg = [
  491. startAngle,
  492. endAngle,
  493. 0,
  494. radius,
  495. this.centerx + explodedX,
  496. this.centery + explodedY
  497. ]);
  498. this.startRadians += segmentRadians;
  499. }
  500. } else {
  501. var sequentialColorIndex = 0;
  502. /*******************************************************
  503. * Draw regular segments here
  504. *******************************************************/
  505. for (var i=0; i<this.data.length; ++i) {
  506. var segmentRadians = (1 / this.data.length) * RG.TWOPI;
  507. if (typeof(this.data[i]) == 'number') {
  508. co.beginPath(); // Begin the segment
  509. co.strokeStyle = prop['chart.strokestyle'];
  510. co.fillStyle = prop['chart.colors'][0];
  511. /*******************************************************
  512. * This allows sequential colors
  513. *******************************************************/
  514. if (prop['chart.colors.sequential']) {
  515. co.fillStyle = prop['chart.colors'][i];
  516. }
  517. var radius = ((this.data[i] - prop['chart.ymin']) / (this.max - prop['chart.ymin'])) * this.radius;
  518. radius = radius * prop['chart.animation.grow.multiplier'];
  519. var startAngle = (this.startRadians * prop['chart.animation.roundrobin.factor']) - RG.HALFPI + margin;
  520. var endAngle = (this.startRadians * prop['chart.animation.roundrobin.factor']) + (segmentRadians * prop['chart.animation.roundrobin.factor']) - RG.HALFPI - margin;
  521. var exploded = this.getexploded(i, startAngle, endAngle, prop['chart.exploded']);
  522. var explodedX = exploded[0];
  523. var explodedY = exploded[1];
  524. co.arc(this.centerx + explodedX,
  525. this.centery + explodedY,
  526. prop['chart.animation.roundrobin.radius'] ? radius * prop['chart.animation.roundrobin.factor'] : radius,
  527. startAngle,
  528. endAngle,
  529. 0);
  530. co.lineTo(this.centerx + explodedX, this.centery + explodedY);
  531. co.closePath(); // End the segment
  532. co.stroke();
  533. co.fill();
  534. // This skirts a double-stroke bug
  535. co.beginPath();
  536. if (endAngle == 0) {
  537. //endAngle = RG.TWOPI;
  538. }
  539. // Store the start and end angles
  540. this.angles.push([
  541. startAngle,
  542. endAngle,
  543. 0,
  544. radius * prop['chart.animation.roundrobin.factor'],
  545. this.centerx + explodedX,
  546. this.centery + explodedY
  547. ]);
  548. /*******************************************************
  549. * Draw a stacked segment
  550. *******************************************************/
  551. } else if (typeof(this.data[i]) == 'object') {
  552. var margin = prop['chart.margin'] / (180 / RG.PI);
  553. // Initialise the angles2 array so there's no undefined error
  554. if (!this.angles2[i]) {
  555. this.angles2[i] = [];
  556. }
  557. for (var j=0; j<this.data[i].length; ++j) {
  558. var startAngle = (this.startRadians * prop['chart.animation.roundrobin.factor']) - RG.HALFPI + margin;
  559. var endAngle = (this.startRadians * prop['chart.animation.roundrobin.factor'])+ (segmentRadians * prop['chart.animation.roundrobin.factor']) - RG.HALFPI - margin;
  560. var exploded = this.getexploded(i, startAngle, endAngle, prop['chart.exploded']);
  561. var explodedX = exploded[0];
  562. var explodedY = exploded[1];
  563. co.strokeStyle = prop['chart.strokestyle'];
  564. co.fillStyle = prop['chart.colors'][j];
  565. // This facilitates sequential color support
  566. if (prop['chart.colors.sequential']) {
  567. co.fillStyle = prop['chart.colors'][sequentialColorIndex++];
  568. }
  569. if (j == 0) {
  570. co.beginPath(); // Begin the segment
  571. var startRadius = 0;
  572. var endRadius = ((this.data[i][j] - prop['chart.ymin']) / (this.max - prop['chart.ymin'])) * this.radius;
  573. endRadius = endRadius * prop['chart.animation.grow.multiplier'];
  574. co.arc(this.centerx + explodedX,
  575. this.centery + explodedY,
  576. prop['chart.animation.roundrobin.radius'] ? endRadius * prop['chart.animation.roundrobin.factor'] : endRadius,
  577. startAngle,
  578. endAngle,
  579. 0);
  580. co.lineTo(this.centerx + explodedX, this.centery + explodedY);
  581. co.closePath(); // End the segment
  582. co.stroke();
  583. co.fill();
  584. this.angles.push([
  585. startAngle,
  586. endAngle,
  587. 0,
  588. endRadius * prop['chart.animation.roundrobin.factor'],
  589. this.centerx + explodedX,
  590. this.centery + explodedY
  591. ]);
  592. this.angles2[i].push([
  593. startAngle,
  594. endAngle,
  595. 0,
  596. endRadius * prop['chart.animation.roundrobin.factor'],
  597. this.centerx + explodedX,
  598. this.centery + explodedY
  599. ]);
  600. } else {
  601. co.beginPath(); // Begin the segment
  602. var startRadius = endRadius; // This comes from the prior iteration of this loop
  603. var endRadius = (((this.data[i][j] - prop['chart.ymin']) / (this.max - prop['chart.ymin'])) * this.radius) + startRadius;
  604. endRadius = endRadius * prop['chart.animation.grow.multiplier'];
  605. co.arc(this.centerx + explodedX,
  606. this.centery + explodedY,
  607. startRadius * prop['chart.animation.roundrobin.factor'],
  608. startAngle,
  609. endAngle,
  610. 0);
  611. co.arc(this.centerx + explodedX,
  612. this.centery + explodedY,
  613. endRadius * prop['chart.animation.roundrobin.factor'],
  614. endAngle,
  615. startAngle,
  616. true);
  617. co.closePath(); // End the segment
  618. co.stroke();
  619. co.fill();
  620. this.angles.push([
  621. startAngle,
  622. endAngle,
  623. startRadius * prop['chart.animation.roundrobin.factor'],
  624. endRadius * prop['chart.animation.roundrobin.factor'],
  625. this.centerx + explodedX,
  626. this.centery + explodedY
  627. ]);
  628. this.angles2[i].push([
  629. startAngle,
  630. endAngle,
  631. startRadius * prop['chart.animation.roundrobin.factor'],
  632. endRadius * prop['chart.animation.roundrobin.factor'],
  633. this.centerx + explodedX,
  634. this.centery + explodedY
  635. ]);
  636. }
  637. }
  638. }
  639. this.startRadians += segmentRadians;
  640. }
  641. }
  642. // Turn off the transparency
  643. if (prop['chart.colors.alpha']) {
  644. co.globalAlpha = 1;
  645. }
  646. // Draw the title if any has been set
  647. if (prop['chart.title']) {
  648. RG.DrawTitle(this,
  649. prop['chart.title'],
  650. (ca.height / 2) - this.radius,
  651. this.centerx,
  652. prop['chart.title.size'] ? prop['chart.title.size'] : prop['chart.text.size'] + 2);
  653. }
  654. };
  655. /**
  656. * Unsuprisingly, draws the labels
  657. */
  658. this.drawLabels =
  659. this.DrawLabels = function ()
  660. {
  661. co.lineWidth = 1;
  662. var key = prop['chart.key'];
  663. if (key && key.length) {
  664. RG.DrawKey(this, key, prop['chart.colors']);
  665. }
  666. // Set the color to black
  667. co.fillStyle = prop['chart.text.color'];
  668. co.strokeStyle = 'black';
  669. var radius = this.radius;
  670. var font = prop['chart.text.font'];
  671. var size = prop['chart.text.size'];
  672. var axes = prop['chart.labels.axes'].toLowerCase();
  673. var decimals = prop['chart.scale.decimals'];
  674. var units_pre = prop['chart.units.pre'];
  675. var units_post = prop['chart.units.post'];
  676. var centerx = this.centerx;
  677. var centery = this.centery;
  678. // Draw any circular labels
  679. if (typeof(prop['chart.labels']) == 'object' && prop['chart.labels']) {
  680. this.DrawCircularLabels(co, prop['chart.labels'], font, size, radius + 10);
  681. }
  682. // Size can be specified seperately for the scale now
  683. if (typeof(prop['chart.text.size.scale']) == 'number') {
  684. size = prop['chart.text.size.scale'];
  685. }
  686. var color = 'rgba(255,255,255,0.8)';
  687. // The "North" axis labels
  688. if (axes.indexOf('n') > -1) {
  689. for (var i=0; i<prop['chart.labels.count']; ++i) {
  690. RG.Text2(this, {'font':font,
  691. 'size':size,
  692. 'x':centerx,
  693. 'y':centery - (radius * ((i+1) / prop['chart.labels.count'])),
  694. 'text':this.scale2.labels[i],
  695. 'valign':'center',
  696. 'halign':'center',
  697. 'bounding':true,
  698. 'boundingFill':color,
  699. 'tag': 'scale'
  700. });
  701. }
  702. }
  703. // The "South" axis labels
  704. if (axes.indexOf('s') > -1) {
  705. for (var i=0; i<prop['chart.labels.count']; ++i) {
  706. RG.Text2(this, {'font':font,
  707. 'size':size,
  708. 'x':centerx,
  709. 'y':centery + (radius * ((i+1) / prop['chart.labels.count'])),
  710. 'text':this.scale2.labels[i],
  711. 'valign':'center',
  712. 'halign':'center',
  713. 'bounding':true,
  714. 'boundingFill':color,
  715. 'tag': 'scale'
  716. });
  717. }
  718. }
  719. // The "East" axis labels
  720. if (axes.indexOf('e') > -1) {
  721. for (var i=0; i<prop['chart.labels.count']; ++i) {
  722. RG.Text2(this, {'font':font,
  723. 'size':size,
  724. 'x':centerx + (radius * ((i+1) / prop['chart.labels.count'])),
  725. 'y':centery,
  726. 'text':this.scale2.labels[i],
  727. 'valign':'center',
  728. 'halign':'center',
  729. 'bounding':true,
  730. 'boundingFill':color,
  731. 'tag': 'scale'
  732. });
  733. }
  734. }
  735. // The "West" axis labels
  736. if (axes.indexOf('w') > -1) {
  737. for (var i=0; i<prop['chart.labels.count']; ++i) {
  738. RG.Text2(this, {'font':font,
  739. 'size':size,
  740. 'x':centerx - (radius * ((i+1) / prop['chart.labels.count'])),
  741. 'y':centery,
  742. 'text':this.scale2.labels[i],
  743. 'valign':'center',
  744. 'halign':'center',
  745. 'bounding':true,
  746. 'boundingFill':color,
  747. 'tag': 'scale'
  748. });
  749. }
  750. }
  751. if (axes.length > 0) {
  752. RG.Text2(this, {'font':font,
  753. 'size':size,
  754. 'x':centerx,
  755. 'y':centery,
  756. 'text':typeof(prop['chart.ymin']) == 'number' ? RG.number_format(this, Number(prop['chart.ymin']).toFixed(prop['chart.scale.decimals']), units_pre, units_post) : '0',
  757. 'valign':'center',
  758. 'halign':'center',
  759. 'bounding':true,
  760. 'boundingFill':color,
  761. 'tag': 'scale'
  762. });
  763. }
  764. };
  765. /**
  766. * Draws the circular labels that go around the charts
  767. *
  768. * @param labels array The labels that go around the chart
  769. */
  770. this.drawCircularLabels =
  771. this.DrawCircularLabels = function (co, labels, font, size, radius)
  772. {
  773. var variant = prop['chart.variant'];
  774. var position = prop['chart.labels.position'];
  775. var radius = radius + 5 + prop['chart.labels.offset'];
  776. var centerx = this.centerx;
  777. var centery = this.centery;
  778. for (var i=0; i<labels.length; ++i) {
  779. if (typeof(variant) == 'string' && variant == 'non-equi-angular') {
  780. var a = Number(this.angles[i][0]) + ((this.angles[i][1] - this.angles[i][0]) / 2);
  781. } else {
  782. var a = (RG.TWOPI / labels.length) * (i + 1) - (RG.TWOPI / (labels.length * 2));
  783. var a = a - RG.HALFPI + (prop['chart.labels.position'] == 'edge' ? ((RG.TWOPI / labels.length) / 2) : 0);
  784. }
  785. var x = centerx + (Math.cos(a) * radius);
  786. var y = centery + (Math.sin(a) * radius);
  787. // Horizontal alignment
  788. if (x > centerx) {
  789. halign = 'left';
  790. } else if (Math.round(x) == centerx) {
  791. halign = 'center';
  792. } else {
  793. halign = 'right';
  794. }
  795. RG.Text2(this, {'font':font,
  796. 'size':size,
  797. 'x':x,
  798. 'y':y,
  799. 'text':String(labels[i]),
  800. 'halign':halign,
  801. 'valign':'center',
  802. 'tag': 'labels'
  803. });
  804. }
  805. };
  806. /**
  807. * This function is for use with circular graph types, eg the Pie or Rose. Pass it your event object
  808. * and it will pass you back the corresponding segment details as an array:
  809. *
  810. * [x, y, r, startAngle, endAngle]
  811. *
  812. * Angles are measured in degrees, and are measured from the "east" axis (just like the canvas).
  813. *
  814. * @param object e Your event object
  815. */
  816. this.getShape =
  817. this.getSegment = function (e)
  818. {
  819. RG.FixEventObject(e);
  820. var angles = this.angles;
  821. var ret = [];
  822. /**
  823. * Go through all of the angles checking each one
  824. */
  825. for (var i=0; i<angles.length ; ++i) {
  826. var angleStart = angles[i][0];
  827. var angleEnd = angles[i][1];
  828. var radiusStart = angles[i][2];
  829. var radiusEnd = angles[i][3];
  830. var centerX = angles[i][4];
  831. var centerY = angles[i][5];
  832. var mouseXY = RG.getMouseXY(e);
  833. var mouseX = mouseXY[0] - centerX;
  834. var mouseY = mouseXY[1] - centerY;
  835. // New click testing (the 0.01 is there because Opera doesn't like 0 as the radius)
  836. co.beginPath();
  837. co.arc(centerX, centerY, radiusStart ? radiusStart : 0.01, angleStart, angleEnd, false);
  838. co.arc(centerX, centerY, radiusEnd, angleEnd, angleStart, true);
  839. co.closePath();
  840. // No stroke() or fill()
  841. if (co.isPointInPath(mouseXY[0], mouseXY[1])) {
  842. angles[i][6] = i;
  843. if (RG.parseTooltipText) {
  844. var tooltip = RG.parseTooltipText(prop['chart.tooltips'], angles[i][6]);
  845. }
  846. // Add the textual keys
  847. angles[i]['object'] = this;
  848. angles[i]['x'] = angles[i][4];
  849. angles[i]['y'] = angles[i][5];
  850. angles[i]['angle.start'] = angles[i][0];
  851. angles[i]['angle.end'] = angles[i][1];
  852. angles[i]['radius.start'] = angles[i][2];
  853. angles[i]['radius.end'] = angles[i][3];
  854. angles[i]['index'] = angles[i][6];
  855. angles[i]['tooltip'] = tooltip ? tooltip : null;
  856. return angles[i];
  857. }
  858. }
  859. return null;
  860. };
  861. /**
  862. * Returns any exploded for a particular segment
  863. */
  864. this.getExploded =
  865. this.getexploded = function (index, startAngle, endAngle, exploded)
  866. {
  867. var explodedx, explodedy;
  868. /**
  869. * Retrieve any exploded - the exploded can be an array of numbers or a single number
  870. * (which is applied to all segments)
  871. */
  872. if (typeof(exploded) == 'object' && typeof(exploded[index]) == 'number') {
  873. explodedx = Math.cos(((endAngle - startAngle) / 2) + startAngle) * exploded[index];
  874. explodedy = Math.sin(((endAngle - startAngle) / 2) + startAngle) * exploded[index];
  875. } else if (typeof(exploded) == 'number') {
  876. explodedx = Math.cos(((endAngle - startAngle) / 2) + startAngle) * exploded;
  877. explodedy = Math.sin(((endAngle - startAngle) / 2) + startAngle) * exploded;
  878. } else {
  879. explodedx = 0;
  880. explodedy = 0;
  881. }
  882. return [explodedx, explodedy];
  883. };
  884. /**
  885. * This function facilitates the installation of tooltip event listeners if
  886. * tooltips are defined.
  887. */
  888. this.allowTooltips =
  889. this.AllowTooltips = function ()
  890. {
  891. // Preload any tooltip images that are used in the tooltips
  892. RG.PreLoadTooltipImages(this);
  893. /**
  894. * This installs the window mousedown event listener that lears any
  895. * highlight that may be visible.
  896. */
  897. RG.InstallWindowMousedownTooltipListener(this);
  898. /**
  899. * This installs the canvas mousemove event listener. This function
  900. * controls the pointer shape.
  901. */
  902. RG.InstallCanvasMousemoveTooltipListener(this);
  903. /**
  904. * This installs the canvas mouseup event listener. This is the
  905. * function that actually shows the appropriate tooltip (if any).
  906. */
  907. RG.InstallCanvasMouseupTooltipListener(this);
  908. };
  909. /**
  910. * Each object type has its own Highlight() function which highlights the appropriate shape
  911. *
  912. * @param object shape The shape to highlight
  913. */
  914. this.highlight =
  915. this.Highlight = function (shape)
  916. {
  917. if (prop['chart.tooltips.highlight']) {
  918. // Add the new segment highlight
  919. co.beginPath();
  920. co.strokeStyle = prop['chart.highlight.stroke'];
  921. co.fillStyle = prop['chart.highlight.fill'];
  922. co.arc(shape['x'], shape['y'], shape['radius.end'], shape['angle.start'], shape['angle.end'], false);
  923. if (shape['radius.start'] > 0) {
  924. co.arc(shape['x'], shape['y'], shape['radius.start'], shape['angle.end'], shape['angle.start'], true);
  925. } else {
  926. co.lineTo(shape['x'], shape['y']);
  927. }
  928. co.closePath();
  929. co.stroke();
  930. co.fill();
  931. }
  932. };
  933. /**
  934. * The getObjectByXY() worker method. Don't call this call:
  935. *
  936. * RGraph.ObjectRegistry.getObjectByXY(e)
  937. *
  938. * @param object e The event object
  939. */
  940. this.getObjectByXY = function (e)
  941. {
  942. var mouseXY = RGraph.getMouseXY(e);
  943. // Work out the radius
  944. var radius = RG.getHypLength(this.centerx, this.centery, mouseXY[0], mouseXY[1]);
  945. if (
  946. mouseXY[0] > (this.centerx - this.radius)
  947. && mouseXY[0] < (this.centerx + this.radius)
  948. && mouseXY[1] > (this.centery - this.radius)
  949. && mouseXY[1] < (this.centery + this.radius)
  950. && radius <= this.radius
  951. ) {
  952. return this;
  953. }
  954. };
  955. /**
  956. * This function positions a tooltip when it is displayed
  957. *
  958. * @param obj object The chart object
  959. * @param int x The X coordinate specified for the tooltip
  960. * @param int y The Y coordinate specified for the tooltip
  961. * @param objec tooltip The tooltips DIV element
  962. */
  963. this.positionTooltip = function (obj, x, y, tooltip, idx)
  964. {
  965. var coordX = obj.angles[idx][4];
  966. var coordY = obj.angles[idx][5];
  967. var angleStart = obj.angles[idx][0];
  968. var angleEnd = obj.angles[idx][1];
  969. var radius = ((obj.angles[idx][3] - obj.angles[idx][2]) / 2) + obj.angles[idx][2];
  970. var angleCenter = ((angleEnd - angleStart) / 2) + angleStart;
  971. var canvasXY = RG.getCanvasXY(obj.canvas);
  972. var gutterLeft = this.gutterLeft;
  973. var gutterTop = this.gutterTop;
  974. var width = tooltip.offsetWidth;
  975. var height = tooltip.offsetHeight;
  976. // By default any overflow is hidden
  977. tooltip.style.overflow = '';
  978. // The arrow
  979. var img = new Image();
  980. img.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAAFCAYAAACjKgd3AAAARUlEQVQYV2NkQAN79+797+RkhC4M5+/bd47B2dmZEVkBCgcmgcsgbAaA9GA1BCSBbhAuA/AagmwQPgMIGgIzCD0M0AMMAEFVIAa6UQgcAAAAAElFTkSuQmCC';
  981. img.style.position = 'absolute';
  982. img.id = '__rgraph_tooltip_pointer__';
  983. img.style.top = (tooltip.offsetHeight - 2) + 'px';
  984. tooltip.appendChild(img);
  985. // Reposition the tooltip if at the edges:
  986. // LEFT edge
  987. if ((canvasXY[0] + coordX + (Math.cos(angleCenter) * radius) - (width / 2)) < 10) {
  988. tooltip.style.left = (canvasXY[0] + coordX + (Math.cos(angleCenter) * radius)- (width * 0.1)) + 'px';
  989. tooltip.style.top = (canvasXY[1] + coordY + (Math.sin(angleCenter) * radius)- height - 5) + 'px';
  990. img.style.left = ((width * 0.1) - 8.5) + 'px';
  991. // RIGHT edge
  992. } else if ((canvasXY[0] + coordX + (Math.cos(angleCenter) * radius) + (width / 2)) > (doc.body.offsetWidth - 10) ) {
  993. tooltip.style.left = (canvasXY[0] + coordX + (Math.cos(angleCenter) * radius) - (width * 0.9)) + 'px';
  994. tooltip.style.top = (canvasXY[1] + coordY + (Math.sin(angleCenter) * radius)- height - 5) + 'px';
  995. img.style.left = ((width * 0.9) - 8.5) + 'px';
  996. // Default positioning - CENTERED
  997. } else {
  998. tooltip.style.left = (canvasXY[0] + coordX + (Math.cos(angleCenter) * radius)- (width / 2)) + 'px';
  999. tooltip.style.top = (canvasXY[1] + coordY + (Math.sin(angleCenter) * radius)- height - 5) + 'px';
  1000. img.style.left = ((width * 0.5) - 8.5) + 'px';
  1001. }
  1002. };
  1003. /**
  1004. * This method gives you the relevant radius for a particular value
  1005. *
  1006. * @param number value The relevant value to get the radius for
  1007. */
  1008. this.getRadius = function (value)
  1009. {
  1010. // Range checking (the Rose minimum is always 0)
  1011. if (value < 0 || value > this.max) {
  1012. return null;
  1013. }
  1014. var r = (value / this.max) * this.radius;
  1015. return r;
  1016. };
  1017. /**
  1018. * This allows for easy specification of gradients
  1019. */
  1020. this.parseColors = function ()
  1021. {
  1022. // Save the original colors so that they can be restored when the canvas is reset
  1023. if (this.original_colors.length === 0) {
  1024. this.original_colors['chart.colors'] = RG.array_clone(prop['chart.colors']);
  1025. this.original_colors['chart.key.colors'] = RG.array_clone(prop['chart.key.colors']);
  1026. this.original_colors['chart.text.color'] = RG.array_clone(prop['chart.text.color']);
  1027. this.original_colors['chart.title.color'] = RG.array_clone(prop['chart.title.color']);
  1028. this.original_colors['chart.highlight.stroke'] = RG.array_clone(prop['chart.highlight.stroke']);
  1029. this.original_colors['chart.highlight.fill'] = RG.array_clone(prop['chart.highlight.fill']);
  1030. }
  1031. for (var i=0; i<prop['chart.colors'].length; ++i) {
  1032. prop['chart.colors'][i] = this.parseSingleColorForGradient(prop['chart.colors'][i]);
  1033. }
  1034. /**
  1035. * Key colors
  1036. */
  1037. if (!RG.is_null(prop['chart.key.colors'])) {
  1038. for (var i=0; i<prop['chart.key.colors'].length; ++i) {
  1039. prop['chart.key.colors'][i] = this.parseSingleColorForGradient(prop['chart.key.colors'][i]);
  1040. }
  1041. }
  1042. prop['chart.text.color'] = this.parseSingleColorForGradient(prop['chart.text.color']);
  1043. prop['chart.title.color'] = this.parseSingleColorForGradient(prop['chart.title.color']);
  1044. prop['chart.highlight.fill'] = this.parseSingleColorForGradient(prop['chart.highlight.fill']);
  1045. prop['chart.highlight.stroke'] = this.parseSingleColorForGradient(prop['chart.highlight.stroke']);
  1046. };
  1047. /**
  1048. * This parses a single color value
  1049. */
  1050. this.parseSingleColorForGradient = function (color)
  1051. {
  1052. if (!color || typeof(color) != 'string') {
  1053. return color;
  1054. }
  1055. if (color.match(/^gradient\((.*)\)$/i)) {
  1056. var parts = RegExp.$1.split(':');
  1057. // Create the gradient
  1058. //var grad = context.createLinearGradient(0,0,canvas.width,0);
  1059. var grad = co.createRadialGradient(this.centerx, this.centery, 0, this.centerx, this.centery, this.radius);
  1060. var diff = 1 / (parts.length - 1);
  1061. grad.addColorStop(0, RG.trim(parts[0]));
  1062. for (var j=1; j<parts.length; ++j) {
  1063. grad.addColorStop(j * diff, RG.trim(parts[j]));
  1064. }
  1065. }
  1066. return grad ? grad : color;
  1067. };
  1068. /**
  1069. * This function handles highlighting an entire data-series for the interactive
  1070. * key
  1071. *
  1072. * @param int index The index of the data series to be highlighted
  1073. */
  1074. this.interactiveKeyHighlight = function (index)
  1075. {
  1076. var segments = this.angles2;
  1077. for (var i=0; i<this.angles2.length; i+=1) {
  1078. co.beginPath();
  1079. co.lineWidth = 2;
  1080. co.fillStyle = prop['chart.key.interactive.highlight.chart.fill'];
  1081. co.strokeStyle = prop['chart.key.interactive.highlight.chart.stroke'];
  1082. co.arc(segments[i][index][4], segments[i][index][5], segments[i][index][2], segments[i][index][0], segments[i][index][1], false);
  1083. co.arc(segments[i][index][4], segments[i][index][5], segments[i][index][3], segments[i][index][1], segments[i][index][0], true);
  1084. co.closePath();
  1085. co.fill();
  1086. co.stroke();
  1087. }
  1088. return/*
  1089. if (segments) {
  1090. for (var i=0; i<segments.length; i+=1) {
  1091. }
  1092. }
  1093. */
  1094. };
  1095. /**
  1096. * Using a function to add events makes it easier to facilitate method chaining
  1097. *
  1098. * @param string type The type of even to add
  1099. * @param function func
  1100. */
  1101. this.on = function (type, func)
  1102. {
  1103. if (type.substr(0,2) !== 'on') {
  1104. type = 'on' + type;
  1105. }
  1106. this[type] = func;
  1107. return this;
  1108. };
  1109. /**
  1110. * This function runs once only
  1111. * (put at the end of the file (before any effects))
  1112. */
  1113. this.firstDrawFunc = function ()
  1114. {
  1115. };
  1116. /**
  1117. * Rose chart explode
  1118. *
  1119. * Explodes the Rose chart - gradually incrementing the size of the chart.explode property
  1120. *
  1121. * @param object Optional options for the effect. You can pass in frames here - such as:
  1122. * myRose.roundRobin({frames: 60}; function () {alert('Done!');})
  1123. * @param function A callback function which is called when the effect is finished
  1124. */
  1125. this.explode = function ()
  1126. {
  1127. var obj = this;
  1128. var opt = arguments[0] || {};
  1129. var callback = arguments[1] || function (){};
  1130. var frames = opt.frames ? opt.frames : 30;
  1131. var frame = 0;
  1132. var explodedMax = ma.max(ca.width, ca.height);
  1133. var exploded = Number(this.Get('exploded'));
  1134. function iterator ()
  1135. {
  1136. exploded = (frame / frames) * explodedMax;
  1137. // Set the new value
  1138. obj.Set('exploded', exploded);
  1139. RG.clear(ca);
  1140. RG.redrawCanvas(ca);
  1141. if (frame++ < frames) {
  1142. RG.Effects.updateCanvas(iterator);
  1143. } else {
  1144. callback(obj);
  1145. }
  1146. }
  1147. iterator();
  1148. return this;
  1149. };
  1150. /**
  1151. * RoundRobin
  1152. *
  1153. * This effect is similar to the Pie chart RoundRobin effect
  1154. *
  1155. * @param object Optional options for the effect. You can pass in frames here - such as:
  1156. * myRose.roundRobin({frames: 60}; function () {alert('Done!');})
  1157. * @param function A callback function which is called when the effect is finished
  1158. */
  1159. this.roundrobin =
  1160. this.roundRobin = function ()
  1161. {
  1162. var obj = this;
  1163. var opt = arguments[0] || {}
  1164. var frames = opt.frames || 30;
  1165. var frame = 0;
  1166. var original_margin = prop['chart.margin'];
  1167. var margin = (360 / this.data.length) / 2;
  1168. var callback = arguments[1] || function () {};
  1169. this.Set('chart.margin', margin);
  1170. this.Set('chart.animation.roundrobin.factor', 0);
  1171. function iterator ()
  1172. {
  1173. RG.clear(obj.canvas);
  1174. RG.redrawCanvas(obj.canvas);
  1175. if (frame++ < frames) {
  1176. obj.set('animation.roundrobin.factor', frame / frames);
  1177. obj.set('margin', (frame / frames) * original_margin);
  1178. RG.Effects.updateCanvas(iterator);
  1179. } else {
  1180. obj.set('animation.roundrobin.factor', 1);
  1181. obj.set('margin', original_margin);
  1182. callback(obj);
  1183. }
  1184. }
  1185. iterator();
  1186. return this;
  1187. };
  1188. /**
  1189. * Rose chart implode
  1190. *
  1191. * Implodes the Rose chart - gradually decreasing the size of the chart.explode property. It starts at the largest of
  1192. * the canvas width./height
  1193. *
  1194. * @param object Optional options for the effect. You can pass in frames here - such as:
  1195. * myRose.implode({frames: 60}; function () {alert('Done!');})
  1196. * @param function A callback function which is called when the effect is finished
  1197. */
  1198. this.implode = function ()
  1199. {
  1200. var obj = this;
  1201. var opt = arguments[0] || {};
  1202. var callback = arguments[1] || function (){};
  1203. var frames = opt.frames || 30;
  1204. var frame = 0;
  1205. var explodedMax = ma.max(ca.width, ca.height);
  1206. var exploded = explodedMax;
  1207. function iterator ()
  1208. {
  1209. exploded = explodedMax - ((frame / frames) * explodedMax);
  1210. // Set the new value
  1211. obj.Set('exploded', exploded);
  1212. RG.clear(ca);
  1213. RG.redrawCanvas(ca);
  1214. if (frame++ < frames) {
  1215. RG.Effects.updateCanvas(iterator);
  1216. } else {
  1217. RG.clear(obj.canvas);
  1218. RG.redrawCanvas(obj.canvas);
  1219. callback(obj);
  1220. }
  1221. }
  1222. iterator();
  1223. return this;
  1224. };
  1225. /**
  1226. * Rose chart Grow
  1227. *
  1228. * This effect gradually increases the size of the Rose chart
  1229. *
  1230. * @param object Optional options for the effect. You can pass in frames here - such as:
  1231. * myRose.grow({frames: 60}; function () {alert('Done!');})
  1232. * @param function A callback function which is called when the effect is finished
  1233. */
  1234. this.grow = function ()
  1235. {
  1236. var obj = this;
  1237. var opt = arguments[0] || {};
  1238. var callback = arguments[1] || function (){};
  1239. var frames = opt.frames || 30;
  1240. var frame = 0;
  1241. function iterator ()
  1242. {
  1243. obj.Set('animation.grow.multiplier', frame / frames);
  1244. RG.clear(ca);
  1245. RG.redrawCanvas(ca);
  1246. if (frame < frames) {
  1247. frame++;
  1248. RG.Effects.updateCanvas(iterator);
  1249. } else {
  1250. callback(obj);
  1251. }
  1252. }
  1253. iterator();
  1254. return this;
  1255. };
  1256. /**
  1257. * Register this object
  1258. */
  1259. RG.Register(this);
  1260. };