RGraph.pie.js 50 KB

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