RGraph.pie.js 58 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611
  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 pie chart constructor
  14. *
  15. * @param data array The data to be represented on the Pie chart
  16. */
  17. RGraph.Pie = function (id, data)
  18. {
  19. var tmp = RGraph.getCanvasTag(id);
  20. // Get the canvas and context objects
  21. this.id = tmp[0];
  22. this.canvas = tmp[1];
  23. this.context = this.canvas.getContext ? this.canvas.getContext("2d", {alpha: (typeof id === 'object' && id.alpha === false) ? false : true}) : null;
  24. this.canvas.__object__ = this;
  25. this.total = 0;
  26. this.subTotal = 0;
  27. this.angles = [];
  28. this.data = data;
  29. this.properties = [];
  30. this.type = 'pie';
  31. this.isRGraph = true;
  32. this.coords = [];
  33. this.coords.key = [];
  34. this.coordsSticks = [];
  35. this.coordsText = [];
  36. this.uid = RGraph.CreateUID();
  37. this.canvas.uid = this.canvas.uid ? this.canvas.uid : RGraph.CreateUID();
  38. this.colorsParsed = false;
  39. this.original_colors = [];
  40. this.firstDraw = true; // After the first draw this will be false
  41. /**
  42. * Compatibility with older browsers
  43. */
  44. //RGraph.OldBrowserCompat(this.context);
  45. this.properties =
  46. {
  47. 'chart.colors': ['Gradient(red:#fcc)', 'Gradient(#ddd:#eee)', 'Gradient(#0f0:#cfc)', 'Gradient(blue:#ccf)', 'Gradient(#FB7BA3:#FCC7EE)', 'Gradient(yellow:#ffc)', 'Gradient(#000:#ccc)', 'Gradient(#EE9D80:#FEE5C8)', 'Gradient(cyan:#ccf)','Gradient(#9E7BF6:#C7B6D2)','Gradient(#78CAEA:#C5FBFD)','Gradient(#E284E9:#FDC4FF)','Gradient(white:#ccf)','Gradient(blue:#ccf)','Gradient(#9E7BF6:#C7B6D2)'],
  48. 'chart.strokestyle': 'white',
  49. 'chart.linewidth': 3,
  50. 'chart.labels': [],
  51. 'chart.labels.sticks': false,
  52. 'chart.labels.sticks.length': 7,
  53. 'chart.labels.sticks.color': '#aaa',
  54. 'chart.labels.sticks.hlength': 5,
  55. 'chart.labels.ingraph': null,
  56. 'chart.labels.ingraph.font': null,
  57. 'chart.labels.ingraph.size': null,
  58. 'chart.labels.ingraph.specific':null,
  59. 'chart.gutter.left': 25,
  60. 'chart.gutter.right': 25,
  61. 'chart.gutter.top': 25,
  62. 'chart.gutter.bottom': 25,
  63. 'chart.title': '',
  64. 'chart.title.background': null,
  65. 'chart.title.hpos': null,
  66. 'chart.title.vpos': 0.5,
  67. 'chart.title.bold': true,
  68. 'chart.title.font': null,
  69. 'chart.title.x': null,
  70. 'chart.title.y': null,
  71. 'chart.title.halign': null,
  72. 'chart.title.valign': null,
  73. 'chart.shadow': true,
  74. 'chart.shadow.color': '#aaa',
  75. 'chart.shadow.offsetx': 0,
  76. 'chart.shadow.offsety': 0,
  77. 'chart.shadow.blur': 15,
  78. 'chart.text.size': 10,
  79. 'chart.text.color': 'black',
  80. 'chart.text.font': 'Arial',
  81. 'chart.contextmenu': null,
  82. 'chart.tooltips': null,
  83. 'chart.tooltips.event': 'onclick',
  84. 'chart.tooltips.effect': 'fade',
  85. 'chart.tooltips.css.class': 'RGraph_tooltip',
  86. 'chart.tooltips.highlight': true,
  87. 'chart.highlight.style': '2d',
  88. 'chart.highlight.style.2d.fill': 'rgba(255,255,255,0.7)',
  89. 'chart.highlight.style.2d.stroke': 'rgba(255,255,255,0.7)',
  90. 'chart.centerx': null,
  91. 'chart.centery': null,
  92. 'chart.radius': null,
  93. 'chart.border': false,
  94. 'chart.border.color': 'rgba(255,255,255,0.5)',
  95. 'chart.key': null,
  96. 'chart.key.background': 'white',
  97. 'chart.key.position': 'graph',
  98. 'chart.key.halign': 'right',
  99. 'chart.key.shadow': false,
  100. 'chart.key.shadow.color': '#666',
  101. 'chart.key.shadow.blur': 3,
  102. 'chart.key.shadow.offsetx': 2,
  103. 'chart.key.shadow.offsety': 2,
  104. 'chart.key.position.gutter.boxed': false,
  105. 'chart.key.position.x': null,
  106. 'chart.key.position.y': null,
  107. 'chart.key.color.shape': 'square',
  108. 'chart.key.rounded': true,
  109. 'chart.key.linewidth': 1,
  110. 'chart.key.colors': null,
  111. 'chart.key.interactive': false,
  112. 'chart.key.interactive.highlight.chart.stroke': 'black',
  113. 'chart.key.interactive.highlight.chart.fill': 'rgba(255,255,255,0.7)',
  114. 'chart.key.interactive.highlight.label': 'rgba(255,0,0,0.2)',
  115. 'chart.key.text.color': 'black',
  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.variant': 'pie',
  132. 'chart.variant.donut.width': null,
  133. 'chart.exploded': [],
  134. 'chart.effect.roundrobin.multiplier': 1,
  135. 'chart.events.click': null,
  136. 'chart.events.mousemove': null,
  137. 'chart.centerpin': null,
  138. 'chart.centerpin.fill': 'gray',
  139. 'chart.centerpin.stroke': 'white',
  140. 'chart.origin': 0 - (Math.PI / 2),
  141. 'chart.events': true,
  142. 'chart.labels.colors': []
  143. }
  144. /**
  145. * Calculate the total
  146. */
  147. for (var i=0,len=data.length; i<len; i++) {
  148. this.total += data[i];
  149. // This loop also creates the $xxx objects - this isn't related to
  150. // the code above but just saves doing another loop through the data
  151. this['$' + i] = {};
  152. }
  153. /**
  154. * Translate half a pixel for antialiasing purposes - but only if it hasn't beeen
  155. * done already
  156. */
  157. if (!this.canvas.__rgraph_aa_translated__) {
  158. this.context.translate(0.5,0.5);
  159. this.canvas.__rgraph_aa_translated__ = true;
  160. }
  161. // Short variable names
  162. var RG = RGraph;
  163. var ca = this.canvas;
  164. var co = ca.getContext('2d');
  165. var prop = this.properties;
  166. var jq = jQuery;
  167. var pa = RG.Path;
  168. var win = window;
  169. var doc = document;
  170. var ma = Math;
  171. /**
  172. * "Decorate" the object with the generic effects if the effects library has been included
  173. */
  174. if (RG.Effects && typeof RG.Effects.decorate === 'function') {
  175. RG.Effects.decorate(this);
  176. }
  177. /**
  178. * A generic setter
  179. */
  180. this.set =
  181. this.Set = function (name, value)
  182. {
  183. name = name.toLowerCase();
  184. /**
  185. * This should be done first - prepend the property name with "chart." if necessary
  186. */
  187. if (name.substr(0,6) != 'chart.') {
  188. name = 'chart.' + name;
  189. }
  190. if (name == 'chart.highlight.style.2d.color') {
  191. name = 'chart.highlight.style.2d.fill';
  192. }
  193. prop[name] = value;
  194. return this;
  195. };
  196. /**
  197. * A generic getter
  198. */
  199. this.get =
  200. this.Get = function (name)
  201. {
  202. /**
  203. * This should be done first - prepend the property name with "chart." if necessary
  204. */
  205. if (name.substr(0,6) != 'chart.') {
  206. name = 'chart.' + name;
  207. }
  208. if (name == 'chart.highlight.style.2d.color') {
  209. name = 'chart.highlight.style.2d.fill';
  210. }
  211. return prop[name];
  212. };
  213. /**
  214. * This draws the pie chart
  215. */
  216. this.draw =
  217. this.Draw = function ()
  218. {
  219. /**
  220. * Fire the onbeforedraw event
  221. */
  222. RG.FireCustomEvent(this, 'onbeforedraw');
  223. // NB: Colors are parsed further down so that the center X/Y can be used
  224. /**
  225. * This is new in May 2011 and facilitates indiviual gutter settings,
  226. * eg chart.gutter.left
  227. */
  228. this.gutterLeft = prop['chart.gutter.left'];
  229. this.gutterRight = prop['chart.gutter.right'];
  230. this.gutterTop = prop['chart.gutter.top'];
  231. this.gutterBottom = prop['chart.gutter.bottom'];
  232. this.radius = this.getRadius();// MUST be first
  233. this.centerx = (this.graph.width / 2) + this.gutterLeft
  234. this.centery = (this.graph.height / 2) + this.gutterTop
  235. this.subTotal = this.properties['chart.origin'];
  236. this.angles = [];
  237. this.coordsText = [];
  238. /**
  239. * Allow specification of a custom radius & center X/Y
  240. */
  241. if (typeof prop['chart.radius'] === 'number') this.radius = prop['chart.radius'];
  242. if (typeof prop['chart.centerx'] === 'number') this.centerx = prop['chart.centerx'];
  243. if (typeof prop['chart.centery'] === 'number') this.centery = prop['chart.centery'];
  244. if (this.radius <= 0) {
  245. return;
  246. }
  247. /**
  248. * Parse the colors for gradients. Its down here so that the center X/Y can be used
  249. */
  250. if (!this.colorsParsed) {
  251. this.parseColors();
  252. // Don't want to do this again
  253. this.colorsParsed = true;
  254. }
  255. /**
  256. * This sets the label colors. Doing it here saves lots of if() conditions in the draw method
  257. */
  258. if (prop['chart.labels.colors'].length < prop['chart.labels'].length) {
  259. while (prop['chart.labels.colors'].length < prop['chart.labels'].length) {
  260. prop['chart.labels.colors'].push(prop['chart.labels.colors'][prop['chart.labels.colors'].length - 1]);
  261. }
  262. } else {
  263. if (typeof prop['chart.labels.colors'] === 'undefined') {
  264. prop['chart.labels.colors'] = [];
  265. }
  266. while (prop['chart.labels.colors'].length < prop['chart.labels'].length) {
  267. prop['chart.labels.colors'].push(prop['chart.text.color']);
  268. }
  269. }
  270. /**
  271. * Draw the title
  272. */
  273. RG.DrawTitle(this,
  274. prop['chart.title'],
  275. (ca.height / 2) - this.radius - 5,
  276. this.centerx,
  277. prop['chart.title.size'] ? prop['chart.title.size'] : prop['chart.text.size'] + 2);
  278. /**
  279. * Draw the shadow if required
  280. *
  281. * ???
  282. */
  283. //if (prop['chart.shadow'] && false) {
  284. //
  285. // var offsetx = doc.all ? prop['chart.shadow.offsetx'] : 0;
  286. // var offsety = doc.all ? prop['chart.shadow.offsety'] : 0;
  287. //
  288. // co.beginPath();
  289. // co.fillStyle = prop['chart.shadow.color'];
  290. //
  291. // co.shadowColor = prop['chart.shadow.color'];
  292. // co.shadowBlur = prop['chart.shadow.blur'];
  293. // co.shadowOffsetX = prop['chart.shadow.offsetx'];
  294. // co.shadowOffsetY = prop['chart.shadow.offsety'];
  295. //
  296. // co.arc(this.centerx + offsetx, this.centery + offsety, this.radius, 0, TWOPI, 0);
  297. //
  298. // co.fill();
  299. //
  300. // // Now turn off the shadow
  301. // RG.NoShadow(this);
  302. //}
  303. /**
  304. * The total of the array of values
  305. */
  306. this.total = RG.array_sum(this.data);
  307. var tot = this.total;
  308. var data = this.data;
  309. for (var i=0,len=this.data.length; i<len; i++) {
  310. var angle = ((data[i] / tot) * RG.TWOPI);
  311. // Draw the segment
  312. this.DrawSegment(angle,prop['chart.colors'][i],i == (len - 1), i);
  313. }
  314. RG.NoShadow(this);
  315. /**
  316. * Redraw the seperating lines
  317. */
  318. if (prop['chart.linewidth'] > 0) {
  319. this.DrawBorders();
  320. }
  321. /**
  322. * Now draw the segments again with shadow turned off. This is always performed,
  323. * not just if the shadow is on.
  324. */
  325. var len = this.angles.length;
  326. var r = this.radius;
  327. for (var action=0; action<2; action+=1) {
  328. for (var i=0; i<len; i++) {
  329. co.beginPath();
  330. var segment = this.angles[i];
  331. if (action === 1) {
  332. co.strokeStyle = typeof(prop['chart.strokestyle']) == 'object' ? prop['chart.strokestyle'][i] : prop['chart.strokestyle'];
  333. }
  334. prop['chart.colors'][i] ? co.fillStyle = prop['chart.colors'][i] : null;
  335. co.lineJoin = 'round';
  336. co.arc(segment[2],
  337. segment[3],
  338. r,
  339. (segment[0]),
  340. (segment[1]),
  341. false);
  342. if (prop['chart.variant'] == 'donut') {
  343. co.arc(segment[2],
  344. segment[3],
  345. typeof(prop['chart.variant.donut.width']) == 'number' ? r - prop['chart.variant.donut.width'] : r / 2,
  346. (segment[1]),
  347. (segment[0]),
  348. true);
  349. } else {
  350. co.lineTo(segment[2], segment[3]);
  351. }
  352. co.closePath();
  353. action === 0 ? co.fill() : co.stroke();
  354. }
  355. }
  356. /**
  357. * Draw label sticks
  358. */
  359. if (prop['chart.labels.sticks']) {
  360. this.DrawSticks();
  361. // Redraw the border going around the Pie chart if the stroke style is NOT white
  362. var strokeStyle = prop['chart.strokestyle'];
  363. //var isWhite = strokeStyle == 'white'
  364. // || strokeStyle == '#fff'
  365. // || strokeStyle == '#fffffff'
  366. // || strokeStyle == 'rgb(255,255,255)'
  367. // || strokeStyle == 'rgba(255,255,255,0)';
  368. // if (!isWhite || (isWhite && this.properties['chart.shadow'])) {
  369. // // Again (?)
  370. // this.DrawBorders();
  371. //}
  372. }
  373. /**
  374. * Draw the labels
  375. */
  376. if (prop['chart.labels']) {
  377. this.DrawLabels();
  378. }
  379. /**
  380. * Draw centerpin if requested
  381. */
  382. if (prop['chart.centerpin']) {
  383. this.DrawCenterpin();
  384. }
  385. /**
  386. * Draw ingraph labels
  387. */
  388. if (prop['chart.labels.ingraph']) {
  389. this.DrawInGraphLabels();
  390. }
  391. /**
  392. * Setup the context menu if required
  393. */
  394. if (prop['chart.contextmenu']) {
  395. RG.ShowContext(this);
  396. }
  397. /**
  398. * If a border is pecified, draw it
  399. */
  400. if (prop['chart.border']) {
  401. co.beginPath();
  402. co.lineWidth = 5;
  403. co.strokeStyle = prop['chart.border.color'];
  404. co.arc(this.centerx,
  405. this.centery,
  406. this.radius - 2,
  407. 0,
  408. RG.TWOPI,
  409. 0);
  410. co.stroke();
  411. }
  412. /**
  413. * Draw the kay if desired
  414. */
  415. if (prop['chart.key'] && prop['chart.key'].length) {
  416. RG.DrawKey(this, prop['chart.key'], prop['chart.colors']);
  417. }
  418. RG.NoShadow(this);
  419. /**
  420. * This function enables resizing
  421. */
  422. if (prop['chart.resizable']) {
  423. RG.AllowResizing(this);
  424. }
  425. /**
  426. * This installs the event listeners
  427. */
  428. if (prop['chart.events'] == true) {
  429. RG.InstallEventListeners(this);
  430. }
  431. /**
  432. * Fire the onfirstdraw event
  433. */
  434. if (this.firstDraw) {
  435. RG.fireCustomEvent(this, 'onfirstdraw');
  436. this.firstDrawFunc();
  437. this.firstDraw = false;
  438. }
  439. /**
  440. * Fire the RGraph ondraw event
  441. */
  442. RG.FireCustomEvent(this, 'ondraw');
  443. return this;
  444. };
  445. /**
  446. * Draws a single segment of the pie chart
  447. *
  448. * @param int degrees The number of degrees for this segment
  449. */
  450. this.drawSegment =
  451. this.DrawSegment = function (radians, color, last, index)
  452. {
  453. // IE7/8/ExCanvas fix (when there's only one segment the Pie chart doesn't display
  454. if (RGraph.ISOLD && radians == RG.TWOPI) {
  455. radians -= 0.0001;
  456. } else if (RGraph.ISOLD && radians == 0) {
  457. radians = 0.001;
  458. }
  459. var context = co;
  460. var canvas = ca;
  461. var subTotal = this.subTotal;
  462. radians = radians * prop['chart.effect.roundrobin.multiplier'];
  463. co.beginPath();
  464. color ? co.fillStyle = color : null;
  465. co.strokeStyle = prop['chart.strokestyle'];
  466. co.lineWidth = 0;
  467. if (prop['chart.shadow']) {
  468. RG.SetShadow(this,
  469. prop['chart.shadow.color'],
  470. prop['chart.shadow.offsetx'],
  471. prop['chart.shadow.offsety'],
  472. prop['chart.shadow.blur']);
  473. }
  474. /**
  475. * Exploded segments
  476. */
  477. if ( (typeof(prop['chart.exploded']) == 'object' && prop['chart.exploded'][index] > 0) || typeof(prop['chart.exploded']) == 'number') {
  478. var explosion = typeof(prop['chart.exploded']) == 'number' ? prop['chart.exploded'] : prop['chart.exploded'][index];
  479. var x = 0;
  480. var y = 0;
  481. var h = explosion;
  482. var t = subTotal + (radians / 2);
  483. var x = (Math.cos(t) * explosion);
  484. var y = (Math.sin(t) * explosion);
  485. var r = this.radius;
  486. co.moveTo(this.centerx + x, this.centery + y);
  487. } else {
  488. var x = 0;
  489. var y = 0;
  490. var r = this.radius;
  491. }
  492. /**
  493. * Calculate the angles
  494. */
  495. var startAngle = subTotal;
  496. var endAngle = ((subTotal + radians));
  497. co.arc(this.centerx + x,
  498. this.centery + y,
  499. r,
  500. startAngle,
  501. endAngle,
  502. 0);
  503. if (prop['chart.variant'] == 'donut') {
  504. co.arc(this.centerx + x,
  505. this.centery + y,
  506. typeof(prop['chart.variant.donut.width']) == 'number' ? r - prop['chart.variant.donut.width'] : r / 2,
  507. endAngle,
  508. startAngle,
  509. true);
  510. } else {
  511. co.lineTo(this.centerx + x, this.centery + y);
  512. }
  513. co.closePath();
  514. // Keep hold of the angles
  515. this.angles.push([subTotal, subTotal + radians, this.centerx + x, this.centery + y]);
  516. //co.stroke();
  517. co.fill();
  518. /**
  519. * Calculate the segment angle
  520. */
  521. this.subTotal += radians;
  522. };
  523. /**
  524. * Draws the graphs labels
  525. */
  526. this.drawLabels =
  527. this.DrawLabels = function ()
  528. {
  529. var hAlignment = 'left';
  530. var vAlignment = 'center';
  531. var labels = prop['chart.labels'];
  532. var context = co;
  533. var font = prop['chart.text.font'];
  534. var text_size = prop['chart.text.size'];
  535. var cx = this.centerx;
  536. var cy = this.centery;
  537. var r = this.radius;
  538. /**
  539. * Turn the shadow off
  540. */
  541. RG.NoShadow(this);
  542. co.fillStyle = 'black';
  543. co.beginPath();
  544. /**
  545. * Draw the labels
  546. */
  547. if (labels && labels.length) {
  548. for (i=0; i<this.angles.length; ++i) {
  549. var segment = this.angles[i];
  550. if (typeof(labels[i]) != 'string' && typeof(labels[i]) != 'number') {
  551. continue;
  552. }
  553. // Move to the centre
  554. co.moveTo(cx,cy);
  555. var a = segment[0] + ((segment[1] - segment[0]) / 2);
  556. var angle = ((segment[1] - segment[0]) / 2) + segment[0];
  557. /**
  558. * Handle the additional "explosion" offset
  559. */
  560. if (typeof prop['chart.exploded'] === 'object' && prop['chart.exploded'][i] || typeof(prop['chart.exploded']) == 'number') {
  561. var t = ((segment[1] - segment[0]) / 2);
  562. var seperation = typeof(prop['chart.exploded']) == 'number' ? prop['chart.exploded'] : prop['chart.exploded'][i];
  563. // Adjust the angles
  564. var explosion_offsetx = (Math.cos(angle) * seperation);
  565. var explosion_offsety = (Math.sin(angle) * seperation);
  566. } else {
  567. var explosion_offsetx = 0;
  568. var explosion_offsety = 0;
  569. }
  570. /**
  571. * Allow for the label sticks
  572. */
  573. if (prop['chart.labels.sticks']) {
  574. explosion_offsetx += (Math.cos(angle) * prop['chart.labels.sticks.length']);
  575. explosion_offsety += (Math.sin(angle) * prop['chart.labels.sticks.length']);
  576. }
  577. /**
  578. * Coords for the text
  579. */
  580. var x = cx + explosion_offsetx + ((r + 10)* Math.cos(a)) + (prop['chart.labels.sticks'] ? (a < RG.HALFPI || a > (RG.TWOPI + RG.HALFPI) ? 2 : -2) : 0)
  581. var y = cy + explosion_offsety + (((r + 10) * Math.sin(a)));
  582. /**
  583. * If sticks are enabled use the endpoints that have been saved
  584. */
  585. if (this.coordsSticks && this.coordsSticks[i]) {
  586. var x = this.coordsSticks[i][4][0] + (x < cx ? -5 : 5);
  587. var y = this.coordsSticks[i][4][1];
  588. }
  589. /**
  590. * Alignment
  591. */
  592. //vAlignment = y < cy ? 'center' : 'center';
  593. vAlignment = 'center';
  594. hAlignment = x < cx ? 'right' : 'left';
  595. co.fillStyle = prop['chart.text.color'];
  596. if ( typeof(prop['chart.labels.colors']) == 'object' && prop['chart.labels.colors'] && prop['chart.labels.colors'][i]) {
  597. co.fillStyle = prop['chart.labels.colors'][i];
  598. }
  599. RG.Text2(this, {'font':font,
  600. 'size':text_size,
  601. 'x':x,
  602. 'y':y,
  603. 'text':labels[i],
  604. 'valign':vAlignment,
  605. 'halign':hAlignment,
  606. 'tag': 'labels'
  607. });
  608. }
  609. co.fill();
  610. }
  611. };
  612. /**
  613. * This function draws the pie chart sticks (for the labels)
  614. */
  615. this.drawSticks =
  616. this.DrawSticks = function ()
  617. {
  618. var context = co;
  619. var offset = prop['chart.linewidth'] / 2;
  620. var exploded = prop['chart.exploded'];
  621. var sticks = prop['chart.labels.sticks'];
  622. var cx = this.centerx;
  623. var cy = this.centery;
  624. var radius = this.radius;
  625. var points = [];
  626. for (var i=0,len=this.angles.length; i<len; ++i) {
  627. var segment = this.angles[i];
  628. // This allows the chart.labels.sticks to be an array as well as a boolean
  629. if (typeof sticks === 'object' && !sticks[i]) {
  630. continue;
  631. }
  632. var radians = segment[1] - segment[0];
  633. co.beginPath();
  634. co.strokeStyle = prop['chart.labels.sticks.color'];
  635. co.lineWidth = 1;
  636. var midpoint = (segment[0] + (radians / 2));
  637. if (typeof exploded === 'object' && exploded[i]) {
  638. var extra = exploded[i];
  639. } else if (typeof exploded === 'number') {
  640. var extra = exploded;
  641. } else {
  642. var extra = 0;
  643. }
  644. //context.lineJoin = 'round';
  645. co.lineWidth = 1;
  646. points[0] = RG.getRadiusEndPoint(cx, cy, midpoint, radius + extra + offset);
  647. points[1] = RG.getRadiusEndPoint(cx, cy, midpoint, radius + prop['chart.labels.sticks.length'] + extra - 5);
  648. points[2] = RG.getRadiusEndPoint(cx, cy, midpoint, radius + prop['chart.labels.sticks.length'] + extra);
  649. points[3] = RG.getRadiusEndPoint(cx, cy, midpoint, radius + prop['chart.labels.sticks.length'] + extra);
  650. points[3][0] += (points[3][0] > cx ? 5 : -5);
  651. points[4] = [
  652. points[2][0] + (points[2][0] > cx ? 5 + prop['chart.labels.sticks.hlength'] : -5 - prop['chart.labels.sticks.hlength']),
  653. points[2][1]
  654. ];
  655. co.moveTo(points[0][0], points[0][1]);
  656. co.lineTo(points[1][0], points[1][1]);
  657. co.quadraticCurveTo(points[2][0], points[2][1], points[3][0], points[3][1]);
  658. co.lineTo(points[4][0], points[4][1]);
  659. co.stroke();
  660. /**
  661. * Save the stick end coords
  662. */
  663. this.coordsSticks[i] = [points[0],points[1], points[2], points[3], points[4]];
  664. }
  665. };
  666. /**
  667. * The (now Pie chart specific) getSegment function
  668. *
  669. * @param object e The event object
  670. */
  671. this.getShape =
  672. this.getSegment = function (e)
  673. {
  674. RG.FixEventObject(e);
  675. // The optional arg provides a way of allowing some accuracy (pixels)
  676. var accuracy = arguments[1] ? arguments[1] : 0;
  677. var canvas = ca;
  678. var context = co;
  679. var mouseCoords = RG.getMouseXY(e);
  680. var mouseX = mouseCoords[0];
  681. var mouseY = mouseCoords[1];
  682. var r = this.radius;
  683. var angles = this.angles;
  684. var ret = [];
  685. for (var i=0,len=angles.length; i<len; ++i) {
  686. // DRAW THE SEGMENT AGAIN SO IT CAN BE TESTED //////////////////////////
  687. co.beginPath();
  688. co.strokeStyle = 'rgba(0,0,0,0)';
  689. co.arc(angles[i][2], angles[i][3], this.radius, angles[i][0], angles[i][1], false);
  690. if (this.type == 'pie' && prop['chart.variant'] == 'donut') {
  691. co.arc(angles[i][2], angles[i][3], (typeof(prop['chart.variant.donut.width']) == 'number' ? this.radius - prop['chart.variant.donut.width'] : this.radius / 2), angles[i][1], angles[i][0], true);
  692. } else {
  693. co.lineTo(angles[i][2], angles[i][3]);
  694. }
  695. co.closePath();
  696. if (!co.isPointInPath(mouseX, mouseY)) {
  697. continue;
  698. }
  699. ////////////////////////////////////////////////////////////////////////
  700. ret[0] = angles[i][2];
  701. ret[1] = angles[i][3];
  702. ret[2] = this.radius;
  703. ret[3] = angles[i][0] - RG.TWOPI;
  704. ret[4] = angles[i][1];
  705. ret[5] = i;
  706. if (ret[3] < 0) ret[3] += RG.TWOPI;
  707. if (ret[4] > RG.TWOPI) ret[4] -= RG.TWOPI;
  708. /**
  709. * Add the tooltip to the returned shape
  710. */
  711. var tooltip = RG.parseTooltipText ? RG.parseTooltipText(prop['chart.tooltips'], ret[5]) : null;
  712. /**
  713. * Now return textual keys as well as numerics
  714. */
  715. ret['object'] = this;
  716. ret['x'] = ret[0];
  717. ret['y'] = ret[1];
  718. ret['radius'] = ret[2];
  719. ret['angle.start'] = ret[3];
  720. ret['angle.end'] = ret[4];
  721. ret['index'] = ret[5];
  722. ret['tooltip'] = tooltip;
  723. return ret;
  724. }
  725. return null;
  726. };
  727. this.drawBorders =
  728. this.DrawBorders = function ()
  729. {
  730. if (prop['chart.linewidth'] > 0) {
  731. co.lineWidth = prop['chart.linewidth'];
  732. co.strokeStyle = prop['chart.strokestyle'];
  733. var r = this.radius;
  734. for (var i=0,len=this.angles.length; i<len; ++i) {
  735. var segment = this.angles[i];
  736. co.beginPath();
  737. co.arc(segment[2],
  738. segment[3],
  739. r,
  740. (segment[0]),
  741. (segment[0] + 0.001),
  742. 0);
  743. co.arc(segment[2],
  744. segment[3],
  745. prop['chart.variant'] == 'donut' ? (typeof(prop['chart.variant.donut.width']) == 'number' ? this.radius - prop['chart.variant.donut.width'] : r / 2): r,
  746. segment[0],
  747. segment[0] + 0.0001,
  748. 0);
  749. co.closePath();
  750. co.stroke();
  751. }
  752. }
  753. };
  754. /**
  755. * Returns the radius of the pie chart
  756. *
  757. * [06-02-2012] Maintained for compatibility ONLY.
  758. */
  759. this.getRadius = function ()
  760. {
  761. this.graph = {width: ca.width - prop['chart.gutter.left'] - prop['chart.gutter.right'], height: ca.height - prop['chart.gutter.top'] - prop['chart.gutter.bottom']}
  762. if (typeof(prop['chart.radius']) == 'number') {
  763. this.radius = prop['chart.radius'];
  764. } else {
  765. this.radius = Math.min(this.graph.width, this.graph.height) / 2;
  766. }
  767. return this.radius;
  768. };
  769. /**
  770. * A programmatic explode function
  771. *
  772. * @param object obj The chart object
  773. * @param number index The zero-indexed number of the segment
  774. * @param number size The size (in pixels) of the explosion
  775. */
  776. this.explodeSegment =
  777. this.Explode = function (index, size)
  778. {
  779. //this.Set('chart.exploded', []);
  780. if (!prop['chart.exploded']) {
  781. prop['chart.exploded'] = [];
  782. }
  783. // If chart.exploded is a number - convert it to an array
  784. if (typeof(prop['chart.exploded']) == 'number') {
  785. var original_explode = prop['chart.exploded'];
  786. var exploded = prop['chart.exploded'];
  787. prop['chart.exploded'] = [];
  788. for (var i=0,len=this.data.length; i<len; ++i) {
  789. prop['chart.exploded'][i] = exploded;
  790. }
  791. }
  792. prop['chart.exploded'][index] = typeof(original_explode) == 'number' ? original_explode : 0;
  793. for (var o=0; o<size; ++o) {
  794. setTimeout(
  795. function ()
  796. {
  797. prop['chart.exploded'][index] += 1;
  798. RG.Clear(ca);
  799. RG.RedrawCanvas(ca);
  800. }, o * (RGraph.ISIE && !RGraph.ISIE10 ? 25 : 16.666));
  801. }
  802. };
  803. /**
  804. * This function highlights a segment
  805. *
  806. * @param array segment The segment information that is returned by the pie.getSegment(e) function
  807. */
  808. this.highlight_segment = function (segment)
  809. {
  810. co.beginPath();
  811. co.strokeStyle = prop['chart.highlight.style.2d.stroke'];
  812. co.fillStyle = prop['chart.highlight.style.2d.fill'];
  813. co.moveTo(segment[0], segment[1]);
  814. co.arc(segment[0], segment[1], segment[2], this.angles[segment[5]][0], this.angles[segment[5]][1], 0);
  815. co.lineTo(segment[0], segment[1]);
  816. co.closePath();
  817. co.stroke();
  818. co.fill();
  819. };
  820. /**
  821. * Each object type has its own Highlight() function which highlights the appropriate shape
  822. *
  823. * @param object shape The shape to highlight
  824. */
  825. this.highlight =
  826. this.Highlight = function (shape)
  827. {
  828. if (prop['chart.tooltips.highlight']) {
  829. /**
  830. * 3D style of highlighting
  831. */
  832. if (prop['chart.highlight.style'] == '3d') {
  833. co.lineWidth = 1;
  834. // This is the extent of the 2D effect. Bigger values will give the appearance of a larger "protusion"
  835. var extent = 2;
  836. // Draw a white-out where the segment is
  837. co.beginPath();
  838. RG.NoShadow(this);
  839. co.fillStyle = 'rgba(0,0,0,0)';
  840. co.arc(shape['x'], shape['y'], shape['radius'], shape['angle.start'], shape['angle.end'], false);
  841. if (prop['chart.variant'] == 'donut') {
  842. co.arc(shape['x'], shape['y'], shape['radius'] / 5, shape['angle.end'], shape['angle.start'], true);
  843. } else {
  844. co.lineTo(shape['x'], shape['y']);
  845. }
  846. co.closePath();
  847. co.fill();
  848. // Draw the new segment
  849. co.beginPath();
  850. co.shadowColor = '#666';
  851. co.shadowBlur = 3;
  852. co.shadowOffsetX = 3;
  853. co.shadowOffsetY = 3;
  854. co.fillStyle = prop['chart.colors'][shape['index']];
  855. co.strokeStyle = prop['chart.strokestyle'];
  856. co.arc(shape['x'] - extent, shape['y'] - extent, shape['radius'], shape['angle.start'], shape['angle.end'], false);
  857. if (prop['chart.variant'] == 'donut') {
  858. co.arc(shape['x'] - extent, shape['y'] - extent, shape['radius'] / 2, shape['angle.end'], shape['angle.start'], true)
  859. } else {
  860. co.lineTo(shape['x'] - extent, shape['y'] - extent);
  861. }
  862. co.closePath();
  863. co.stroke();
  864. co.fill();
  865. // Turn off the shadow
  866. RG.NoShadow(this);
  867. /**
  868. * If a border is defined, redraw that
  869. */
  870. if (prop['chart.border']) {
  871. co.beginPath();
  872. co.strokeStyle = prop['chart.border.color'];
  873. co.lineWidth = 5;
  874. co.arc(shape['x'] - extent, shape['y'] - extent, shape['radius'] - 2, shape['angle.start'], shape['angle.end'], false);
  875. co.stroke();
  876. }
  877. // Default 2D style of highlighting
  878. } else {
  879. co.beginPath();
  880. co.strokeStyle = prop['chart.highlight.style.2d.stroke'];
  881. co.fillStyle = prop['chart.highlight.style.2d.fill'];
  882. if (prop['chart.variant'] == 'donut') {
  883. co.arc(shape['x'], shape['y'], shape['radius'], shape['angle.start'], shape['angle.end'], false);
  884. co.arc(shape['x'], shape['y'], typeof(prop['chart.variant.donut.width']) == 'number' ? this.radius - prop['chart.variant.donut.width'] : shape['radius'] / 2, shape['angle.end'], shape['angle.start'], true);
  885. } else {
  886. co.arc(shape['x'], shape['y'], shape['radius'] + 1, shape['angle.start'], shape['angle.end'], false);
  887. co.lineTo(shape['x'], shape['y']);
  888. }
  889. co.closePath();
  890. //co.stroke();
  891. co.fill();
  892. }
  893. }
  894. };
  895. /**
  896. * The getObjectByXY() worker method. The Pie chart is able to use the
  897. * getShape() method - so it does.
  898. */
  899. this.getObjectByXY = function (e)
  900. {
  901. if (this.getShape(e)) {
  902. return this;
  903. }
  904. };
  905. /**
  906. * Draws the centerpin if requested
  907. */
  908. this.drawCenterpin =
  909. this.DrawCenterpin = function ()
  910. {
  911. if (typeof(prop['chart.centerpin']) == 'number' && prop['chart.centerpin'] > 0) {
  912. var cx = this.centerx;
  913. var cy = this.centery;
  914. co.beginPath();
  915. co.strokeStyle = prop['chart.centerpin.stroke'] ? prop['chart.centerpin.stroke'] : prop['chart.strokestyle'];
  916. co.fillStyle = prop['chart.centerpin.fill'] ? prop['chart.centerpin.fill'] : prop['chart.strokestyle'];
  917. co.moveTo(cx, cy);
  918. co.arc(cx, cy, prop['chart.centerpin'], 0, RG.TWOPI, false);
  919. co.stroke();
  920. co.fill();
  921. }
  922. };
  923. /**
  924. * This function positions a tooltip when it is displayed
  925. *
  926. * @param obj object The chart object
  927. * @param int x The X coordinate specified for the tooltip
  928. * @param int y The Y coordinate specified for the tooltip
  929. * @param objec tooltip The tooltips DIV element
  930. */
  931. this.positionTooltip = function (obj, x, y, tooltip, idx)
  932. {
  933. var coordX = obj.angles[idx][2];
  934. var coordY = obj.angles[idx][3];
  935. var angleStart = obj.angles[idx][0];
  936. var angleEnd = obj.angles[idx][1];
  937. var angleCenter = ((angleEnd - angleStart) / 2) + angleStart;
  938. var canvasXY = RGraph.getCanvasXY(obj.canvas);
  939. var gutterLeft = prop['chart.gutter.left'];
  940. var gutterTop = prop['chart.gutter.top'];
  941. var width = tooltip.offsetWidth;
  942. var height = tooltip.offsetHeight;
  943. var x = canvasXY[0] + this.angles[idx][2] + (Math.cos(angleCenter) * (prop['chart.variant'] == 'donut' && typeof(prop['chart.variant.donut.width']) == 'number' ? ((this.radius - prop['chart.variant.donut.width']) + (prop['chart.variant.donut.width'] / 2)) : (this.radius * (prop['chart.variant'] == 'donut' ? 0.75 : 0.5))));
  944. var y = canvasXY[1] + this.angles[idx][3] + (Math.sin(angleCenter) * (prop['chart.variant'] == 'donut' && typeof(prop['chart.variant.donut.width']) == 'number' ? ((this.radius - prop['chart.variant.donut.width']) + (prop['chart.variant.donut.width'] / 2)) : (this.radius * (prop['chart.variant'] == 'donut' ? 0.75 : 0.5))));
  945. // By default any overflow is hidden
  946. tooltip.style.overflow = '';
  947. // The arrow
  948. var img = new Image();
  949. img.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAAFCAYAAACjKgd3AAAARUlEQVQYV2NkQAN79+797+RkhC4M5+/bd47B2dmZEVkBCgcmgcsgbAaA9GA1BCSBbhAuA/AagmwQPgMIGgIzCD0M0AMMAEFVIAa6UQgcAAAAAElFTkSuQmCC';
  950. img.style.position = 'absolute';
  951. img.id = '__rgraph_tooltip_pointer__';
  952. img.style.top = (tooltip.offsetHeight - 2) + 'px';
  953. tooltip.appendChild(img);
  954. // Reposition the tooltip if at the edges:
  955. // LEFT edge
  956. if ((x - (width / 2)) < 10) {
  957. tooltip.style.left = (x - (width * 0.1)) + 'px';
  958. tooltip.style.top = (y - height - 4) + 'px';
  959. img.style.left = ((width * 0.1) - 8.5) + 'px';
  960. // RIGHT edge
  961. } else if ((x + (width / 2)) > (doc.body.offsetWidth - 10) ) {
  962. tooltip.style.left = (x - (width * 0.9)) + 'px';
  963. tooltip.style.top = (y - height - 4) + 'px';
  964. img.style.left = ((width * 0.9) - 8.5) + 'px';
  965. // Default positioning - CENTERED
  966. } else {
  967. tooltip.style.left = (x - (width / 2)) + 'px';
  968. tooltip.style.top = (y - height - 4) + 'px';
  969. img.style.left = ((width * 0.5) - 8.5) + 'px';
  970. }
  971. };
  972. /**
  973. * This draws Ingraph labels
  974. */
  975. this.drawInGraphLabels =
  976. this.DrawInGraphLabels = function ()
  977. {
  978. var context = co;
  979. var cx = this.centerx;
  980. var cy = this.centery;
  981. if (prop['chart.variant'] == 'donut') {
  982. var r = this.radius * 0.75;
  983. if (typeof(prop['chart.variant.donut.width']) == 'number') {
  984. var r = (this.radius - prop['chart.variant.donut.width']) + (prop['chart.variant.donut.width'] / 2);
  985. }
  986. } else {
  987. var r = this.radius / 2;
  988. }
  989. for (var i=0,len=this.angles.length; i<len; ++i) {
  990. // This handles any explosion that the segment may have
  991. if (typeof(prop['chart.exploded']) == 'object' && typeof(prop['chart.exploded'][i]) == 'number') {
  992. var explosion = prop['chart.exploded'][i];
  993. } else if (typeof(prop['chart.exploded']) == 'number') {
  994. var explosion = parseInt(prop['chart.exploded']);
  995. } else {
  996. var explosion = 0;
  997. }
  998. var angleStart = this.angles[i][0];
  999. var angleEnd = this.angles[i][1];
  1000. var angleCenter = ((angleEnd - angleStart) / 2) + angleStart;
  1001. var coords = RG.getRadiusEndPoint(this.centerx, this.centery, angleCenter, r + (explosion ? explosion : 0) );
  1002. var x = coords[0];
  1003. var y = coords[1];
  1004. var text = prop['chart.labels.ingraph.specific'] && typeof(prop['chart.labels.ingraph.specific'][i]) == 'string' ? prop['chart.labels.ingraph.specific'][i] : RG.number_format(this, this.data[i], prop['chart.labels.ingraph.units.pre'] , prop['chart.labels.ingraph.units.post']);
  1005. if (text) {
  1006. co.beginPath();
  1007. var font = typeof(prop['chart.labels.ingraph.font']) == 'string' ? prop['chart.labels.ingraph.font'] : prop['chart.text.font'];
  1008. var size = typeof(prop['chart.labels.ingraph.size']) == 'number' ? prop['chart.labels.ingraph.size'] : prop['chart.text.size'] + 2;
  1009. RG.Text2(this, {'font':font,
  1010. 'size':size,
  1011. 'x':x,
  1012. 'y':y,
  1013. 'text':text,
  1014. 'valign':'center',
  1015. 'halign':'center',
  1016. 'bounding':true,
  1017. 'boundingFill':'white',
  1018. 'tag':'labels.ingraph'
  1019. });
  1020. co.stroke();
  1021. }
  1022. }
  1023. };
  1024. /**
  1025. * This returns the angle for a value based around the maximum number
  1026. *
  1027. * @param number value The value to get the angle for
  1028. */
  1029. this.getAngle = function (value)
  1030. {
  1031. if (value > this.total) {
  1032. return null;
  1033. }
  1034. var angle = (value / this.total) * RG.TWOPI;
  1035. // Handle the origin (it can br -HALFPI or 0)
  1036. angle += prop['chart.origin'];
  1037. return angle;
  1038. };
  1039. /**
  1040. * This allows for easy specification of gradients
  1041. */
  1042. this.parseColors = function ()
  1043. {
  1044. // Save the original colors so that they can be restored when the canvas is reset
  1045. if (this.original_colors.length === 0) {
  1046. this.original_colors['chart.colors'] = RG.array_clone(prop['chart.colors']);
  1047. this.original_colors['chart.key.colors'] = RG.array_clone(prop['chart.key.colors']);
  1048. this.original_colors['chart.strokestyle'] = RG.array_clone(prop['chart.strokestyle']);
  1049. this.original_colors['chart.highlight.stroke'] = RG.array_clone(prop['chart.highlight.stroke']);
  1050. this.original_colors['chart.highlight.style.2d.fill'] = RG.array_clone(prop['chart.highlight.style.2d.fill']);
  1051. this.original_colors['chart.highlight.style.stroke'] = RG.array_clone(prop['chart.highlight.style.2d.stroke']);
  1052. }
  1053. for (var i=0; i<prop['chart.colors'].length; ++i) {
  1054. prop['chart.colors'][i] = this.parseSingleColorForGradient(prop['chart.colors'][i]);
  1055. }
  1056. var keyColors = prop['chart.key.colors'];
  1057. if (keyColors) {
  1058. for (var i=0; i<keyColors.length; ++i) {
  1059. keyColors[i] = this.parseSingleColorForGradient(keyColors[i]);
  1060. }
  1061. }
  1062. prop['chart.strokestyle'] = this.parseSingleColorForGradient(prop['chart.strokestyle']);
  1063. prop['chart.highlight.stroke'] = this.parseSingleColorForGradient(prop['chart.highlight.stroke']);
  1064. prop['chart.highlight.style.2d.fill'] = this.parseSingleColorForGradient(prop['chart.highlight.style.2d.fill']);
  1065. prop['chart.highlight.style.2d.stroke'] = this.parseSingleColorForGradient(prop['chart.highlight.style.2d.stroke']);
  1066. };
  1067. /**
  1068. * This parses a single color value
  1069. */
  1070. this.parseSingleColorForGradient = function (color)
  1071. {
  1072. if (!color || typeof(color) != 'string') {
  1073. return color;
  1074. }
  1075. if (color.match(/^gradient\((.*)\)$/i)) {
  1076. var parts = RegExp.$1.split(':');
  1077. // If the chart is a donut - the first width should half the total radius
  1078. if (prop['chart.variant'] == 'donut') {
  1079. var radius_start = typeof(prop['chart.variant.donut.width']) == 'number' ? this.radius - prop['chart.variant.donut.width'] : this.radius / 2;
  1080. } else {
  1081. var radius_start = 0;
  1082. }
  1083. // Create the gradient
  1084. var grad = co.createRadialGradient(this.centerx, this.centery, radius_start, this.centerx, this.centery, Math.min(ca.width - prop['chart.gutter.left'] - prop['chart.gutter.right'], ca.height - prop['chart.gutter.top'] - prop['chart.gutter.bottom']) / 2);
  1085. var diff = 1 / (parts.length - 1);
  1086. grad.addColorStop(0, RG.trim(parts[0]));
  1087. for (var j=1; j<parts.length; ++j) {
  1088. grad.addColorStop(j * diff, RG.trim(parts[j]));
  1089. }
  1090. }
  1091. return grad ? grad : color;
  1092. };
  1093. /**
  1094. * This function handles highlighting an entire data-series for the interactive
  1095. * key
  1096. *
  1097. * @param int index The index of the data series to be highlighted
  1098. */
  1099. this.interactiveKeyHighlight = function (index)
  1100. {
  1101. if (this.angles && this.angles[index]) {
  1102. var segment = this.angles[index];
  1103. var x = segment[2];
  1104. var y = segment[3];
  1105. var start = segment[0];
  1106. var end = segment[1];
  1107. co.strokeStyle = prop['chart.key.interactive.highlight.chart.stroke'];
  1108. co.fillStyle = prop['chart.key.interactive.highlight.chart.fill'];
  1109. co.lineWidth = 2;
  1110. co.lineJoin = 'bevel';
  1111. co.beginPath();
  1112. co.moveTo(x, y);
  1113. co.arc(x, y, this.radius, start, end, false);
  1114. co.closePath();
  1115. co.fill();
  1116. co.stroke();
  1117. }
  1118. };
  1119. /**
  1120. * Using a function to add events makes it easier to facilitate method chaining
  1121. *
  1122. * @param string type The type of even to add
  1123. * @param function func
  1124. */
  1125. this.on = function (type, func)
  1126. {
  1127. if (type.substr(0,2) !== 'on') {
  1128. type = 'on' + type;
  1129. }
  1130. this[type] = func;
  1131. return this;
  1132. };
  1133. /**
  1134. * This function runs once only
  1135. * (put at the end of the file (before any effects))
  1136. */
  1137. this.firstDrawFunc = function ()
  1138. {
  1139. };
  1140. /**
  1141. * Pie chart explode
  1142. *
  1143. * Explodes the Pie chart - gradually incrementing the size of the chart.explode property
  1144. *
  1145. * @param object Options for the effect
  1146. * @param function An optional callback function to call when the animation completes
  1147. */
  1148. this.explode = function ()
  1149. {
  1150. var obj = this;
  1151. var opt = arguments[0] ? arguments[0] : {};
  1152. var callback = arguments[1] ? arguments[1] : function () {};
  1153. var frames = opt.frames ? opt.frames : 30;
  1154. var frame = 0;
  1155. var maxExplode = Number(typeof opt.radius === 'number' ? opt.radius : ma.max(ca.width, ca.height));
  1156. var currentExplode = Number(obj.get('exploded')) || 0;
  1157. var step = (maxExplode - currentExplode) / frames;
  1158. // chart.exploded
  1159. var iterator = function ()
  1160. {
  1161. obj.set('exploded', currentExplode + (step * frame) );
  1162. RGraph.clear(obj.canvas);
  1163. RGraph.redrawCanvas(obj.canvas);
  1164. if (frame++ < frames) {
  1165. RGraph.Effects.updateCanvas(iterator);
  1166. } else {
  1167. callback(obj);
  1168. }
  1169. }
  1170. iterator();
  1171. return this;
  1172. };
  1173. /**
  1174. * Pie chart grow
  1175. *
  1176. * Gradually increases the pie chart radius
  1177. *
  1178. * @param object OPTIONAL An object of options
  1179. * @param function OPTIONAL A callback function
  1180. */
  1181. this.grow = function ()
  1182. {
  1183. var obj = this;
  1184. var canvas = obj.canvas;
  1185. var opt = arguments[0] ? arguments[0] : {};
  1186. var frames = opt.frames || 30;
  1187. var frame = 0;
  1188. var callback = arguments[1] ? arguments[1] : function () {};
  1189. var radius = obj.getRadius();
  1190. prop['chart.radius'] = 0;
  1191. var iterator = function ()
  1192. {
  1193. obj.set('chart.radius', (frame / frames) * radius);
  1194. RG.redrawCanvas(ca);
  1195. if (frame++ < frames) {
  1196. RG.Effects.updateCanvas(iterator);
  1197. } else {
  1198. RG.redrawCanvas(obj.canvas);
  1199. callback(obj);
  1200. }
  1201. };
  1202. iterator();
  1203. return this;
  1204. };
  1205. /**
  1206. * RoundRobin
  1207. *
  1208. * This effect does two things:
  1209. * 1. Gradually increases the size of each segment
  1210. * 2. Gradually increases the size of the radius from 0
  1211. *
  1212. * @param object OPTIONAL Options for the effect
  1213. * @param function OPTIONAL A callback function
  1214. */
  1215. this.roundrobin =
  1216. this.roundRobin = function ()
  1217. {
  1218. var obj = this;
  1219. var opt = arguments[0] || {};
  1220. var callback = arguments[1] || function () {};
  1221. var frame = 0;
  1222. var frames = opt.frames || 30;
  1223. var radius = obj.getRadius();
  1224. obj.Set('chart.events', false);
  1225. var iterator = function ()
  1226. {
  1227. obj.set('effect.roundrobin.multiplier', RG.Effects.getEasingMultiplier(frames, frame));
  1228. RGraph.redrawCanvas(ca);
  1229. if (frame++ < frames) {
  1230. RGraph.Effects.updateCanvas(iterator);
  1231. } else {
  1232. // Re-enable the events and redraw the chart.
  1233. obj.set('events', true);
  1234. RGraph.redrawCanvas(obj.canvas);
  1235. callback(obj);
  1236. }
  1237. };
  1238. iterator();
  1239. return this;
  1240. };
  1241. /**
  1242. * Now need to register all chart types. MUST be after the setters/getters are defined
  1243. *
  1244. * *** MUST BE LAST IN THE CONSTRUCTOR ***
  1245. */
  1246. RG.register(this);
  1247. }