jqplot.donutRenderer.js 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805
  1. /**
  2. * jqPlot
  3. * Pure JavaScript plotting plugin using jQuery
  4. *
  5. * Version: @VERSION
  6. * Revision: @REVISION
  7. *
  8. * Copyright (c) 2009-2013 Chris Leonello
  9. * jqPlot is currently available for use in all personal or commercial projects
  10. * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
  11. * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
  12. * choose the license that best suits your project and use it accordingly.
  13. *
  14. * Although not required, the author would appreciate an email letting him
  15. * know of any substantial use of jqPlot. You can reach the author at:
  16. * chris at jqplot dot com or see http://www.jqplot.com/info.php .
  17. *
  18. * If you are feeling kind and generous, consider supporting the project by
  19. * making a donation at: http://www.jqplot.com/donate.php .
  20. *
  21. * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
  22. *
  23. * version 2007.04.27
  24. * author Ash Searle
  25. * http://hexmen.com/blog/2007/03/printf-sprintf/
  26. * http://hexmen.com/js/sprintf.js
  27. * The author (Ash Searle) has placed this code in the public domain:
  28. * "This code is unrestricted: you are free to use it however you like."
  29. *
  30. */
  31. (function($) {
  32. /**
  33. * Class: $.jqplot.DonutRenderer
  34. * Plugin renderer to draw a donut chart.
  35. * x values, if present, will be used as slice labels.
  36. * y values give slice size.
  37. *
  38. * To use this renderer, you need to include the
  39. * donut renderer plugin, for example:
  40. *
  41. * > <script type="text/javascript" src="plugins/jqplot.donutRenderer.js"></script>
  42. *
  43. * Properties described here are passed into the $.jqplot function
  44. * as options on the series renderer. For example:
  45. *
  46. * > plot2 = $.jqplot('chart2', [s1, s2], {
  47. * > seriesDefaults: {
  48. * > renderer:$.jqplot.DonutRenderer,
  49. * > rendererOptions:{
  50. * > sliceMargin: 2,
  51. * > innerDiameter: 110,
  52. * > startAngle: -90
  53. * > }
  54. * > }
  55. * > });
  56. *
  57. * A donut plot will trigger events on the plot target
  58. * according to user interaction. All events return the event object,
  59. * the series index, the point (slice) index, and the point data for
  60. * the appropriate slice.
  61. *
  62. * 'jqplotDataMouseOver' - triggered when user mouseing over a slice.
  63. * 'jqplotDataHighlight' - triggered the first time user mouses over a slice,
  64. * if highlighting is enabled.
  65. * 'jqplotDataUnhighlight' - triggered when a user moves the mouse out of
  66. * a highlighted slice.
  67. * 'jqplotDataClick' - triggered when the user clicks on a slice.
  68. * 'jqplotDataRightClick' - tiggered when the user right clicks on a slice if
  69. * the "captureRightClick" option is set to true on the plot.
  70. */
  71. $.jqplot.DonutRenderer = function(){
  72. $.jqplot.LineRenderer.call(this);
  73. };
  74. $.jqplot.DonutRenderer.prototype = new $.jqplot.LineRenderer();
  75. $.jqplot.DonutRenderer.prototype.constructor = $.jqplot.DonutRenderer;
  76. // called with scope of a series
  77. $.jqplot.DonutRenderer.prototype.init = function(options, plot) {
  78. // Group: Properties
  79. //
  80. // prop: diameter
  81. // Outer diameter of the donut, auto computed by default
  82. this.diameter = null;
  83. // prop: innerDiameter
  84. // Inner diameter of the donut, auto calculated by default.
  85. // If specified will override thickness value.
  86. this.innerDiameter = null;
  87. // prop: thickness
  88. // thickness of the donut, auto computed by default
  89. // Overridden by if innerDiameter is specified.
  90. this.thickness = null;
  91. // prop: padding
  92. // padding between the donut and plot edges, legend, etc.
  93. this.padding = 20;
  94. // prop: sliceMargin
  95. // angular spacing between donut slices in degrees.
  96. this.sliceMargin = 0;
  97. // prop: ringMargin
  98. // pixel distance between rings, or multiple series in a donut plot.
  99. // null will compute ringMargin based on sliceMargin.
  100. this.ringMargin = null;
  101. // prop: fill
  102. // true or false, whether to fil the slices.
  103. this.fill = true;
  104. // prop: shadowOffset
  105. // offset of the shadow from the slice and offset of
  106. // each succesive stroke of the shadow from the last.
  107. this.shadowOffset = 2;
  108. // prop: shadowAlpha
  109. // transparency of the shadow (0 = transparent, 1 = opaque)
  110. this.shadowAlpha = 0.07;
  111. // prop: shadowDepth
  112. // number of strokes to apply to the shadow,
  113. // each stroke offset shadowOffset from the last.
  114. this.shadowDepth = 5;
  115. // prop: highlightMouseOver
  116. // True to highlight slice when moused over.
  117. // This must be false to enable highlightMouseDown to highlight when clicking on a slice.
  118. this.highlightMouseOver = true;
  119. // prop: highlightMouseDown
  120. // True to highlight when a mouse button is pressed over a slice.
  121. // This will be disabled if highlightMouseOver is true.
  122. this.highlightMouseDown = false;
  123. // prop: highlightColors
  124. // an array of colors to use when highlighting a slice.
  125. this.highlightColors = [];
  126. // prop: dataLabels
  127. // Either 'label', 'value', 'percent' or an array of labels to place on the pie slices.
  128. // Defaults to percentage of each pie slice.
  129. this.dataLabels = 'percent';
  130. // prop: showDataLabels
  131. // true to show data labels on slices.
  132. this.showDataLabels = false;
  133. // prop: dataLabelFormatString
  134. // Format string for data labels. If none, '%s' is used for "label" and for arrays, '%d' for value and '%d%%' for percentage.
  135. this.dataLabelFormatString = null;
  136. // prop: dataLabelThreshold
  137. // Threshhold in percentage (0 - 100) of pie area, below which no label will be displayed.
  138. // This applies to all label types, not just to percentage labels.
  139. this.dataLabelThreshold = 3;
  140. // prop: dataLabelPositionFactor
  141. // A Multiplier (0-1) of the pie radius which controls position of label on slice.
  142. // Increasing will slide label toward edge of pie, decreasing will slide label toward center of pie.
  143. this.dataLabelPositionFactor = 0.4;
  144. // prop: dataLabelNudge
  145. // Number of pixels to slide the label away from (+) or toward (-) the center of the pie.
  146. this.dataLabelNudge = 0;
  147. // prop: startAngle
  148. // Angle to start drawing donut in degrees.
  149. // According to orientation of canvas coordinate system:
  150. // 0 = on the positive x axis
  151. // -90 = on the positive y axis.
  152. // 90 = on the negaive y axis.
  153. // 180 or - 180 = on the negative x axis.
  154. this.startAngle = 0;
  155. this.tickRenderer = $.jqplot.DonutTickRenderer;
  156. // Used as check for conditions where donut shouldn't be drawn.
  157. this._drawData = true;
  158. this._type = 'donut';
  159. // if user has passed in highlightMouseDown option and not set highlightMouseOver, disable highlightMouseOver
  160. if (options.highlightMouseDown && options.highlightMouseOver == null) {
  161. options.highlightMouseOver = false;
  162. }
  163. $.extend(true, this, options);
  164. if (this.diameter != null) {
  165. this.diameter = this.diameter - this.sliceMargin;
  166. }
  167. this._diameter = null;
  168. this._innerDiameter = null;
  169. this._radius = null;
  170. this._innerRadius = null;
  171. this._thickness = null;
  172. // references to the previous series in the plot to properly calculate diameters
  173. // and thicknesses of nested rings.
  174. this._previousSeries = [];
  175. this._numberSeries = 1;
  176. // array of [start,end] angles arrays, one for each slice. In radians.
  177. this._sliceAngles = [];
  178. // index of the currenty highlighted point, if any
  179. this._highlightedPoint = null;
  180. // set highlight colors if none provided
  181. if (this.highlightColors.length == 0) {
  182. for (var i=0; i<this.seriesColors.length; i++){
  183. var rgba = $.jqplot.getColorComponents(this.seriesColors[i]);
  184. var newrgb = [rgba[0], rgba[1], rgba[2]];
  185. var sum = newrgb[0] + newrgb[1] + newrgb[2];
  186. for (var j=0; j<3; j++) {
  187. // when darkening, lowest color component can be is 60.
  188. newrgb[j] = (sum > 570) ? newrgb[j] * 0.8 : newrgb[j] + 0.3 * (255 - newrgb[j]);
  189. newrgb[j] = parseInt(newrgb[j], 10);
  190. }
  191. this.highlightColors.push('rgb('+newrgb[0]+','+newrgb[1]+','+newrgb[2]+')');
  192. }
  193. }
  194. plot.postParseOptionsHooks.addOnce(postParseOptions);
  195. plot.postInitHooks.addOnce(postInit);
  196. plot.eventListenerHooks.addOnce('jqplotMouseMove', handleMove);
  197. plot.eventListenerHooks.addOnce('jqplotMouseDown', handleMouseDown);
  198. plot.eventListenerHooks.addOnce('jqplotMouseUp', handleMouseUp);
  199. plot.eventListenerHooks.addOnce('jqplotClick', handleClick);
  200. plot.eventListenerHooks.addOnce('jqplotRightClick', handleRightClick);
  201. plot.postDrawHooks.addOnce(postPlotDraw);
  202. };
  203. $.jqplot.DonutRenderer.prototype.setGridData = function(plot) {
  204. // set gridData property. This will hold angle in radians of each data point.
  205. var stack = [];
  206. var td = [];
  207. var sa = this.startAngle/180*Math.PI;
  208. var tot = 0;
  209. // don't know if we have any valid data yet, so set plot to not draw.
  210. this._drawData = false;
  211. for (var i=0; i<this.data.length; i++){
  212. if (this.data[i][1] != 0) {
  213. // we have data, O.K. to draw.
  214. this._drawData = true;
  215. }
  216. stack.push(this.data[i][1]);
  217. td.push([this.data[i][0]]);
  218. if (i>0) {
  219. stack[i] += stack[i-1];
  220. }
  221. tot += this.data[i][1];
  222. }
  223. var fact = Math.PI*2/stack[stack.length - 1];
  224. for (var i=0; i<stack.length; i++) {
  225. td[i][1] = stack[i] * fact;
  226. td[i][2] = this.data[i][1]/tot;
  227. }
  228. this.gridData = td;
  229. };
  230. $.jqplot.DonutRenderer.prototype.makeGridData = function(data, plot) {
  231. var stack = [];
  232. var td = [];
  233. var tot = 0;
  234. var sa = this.startAngle/180*Math.PI;
  235. // don't know if we have any valid data yet, so set plot to not draw.
  236. this._drawData = false;
  237. for (var i=0; i<data.length; i++){
  238. if (this.data[i][1] != 0) {
  239. // we have data, O.K. to draw.
  240. this._drawData = true;
  241. }
  242. stack.push(data[i][1]);
  243. td.push([data[i][0]]);
  244. if (i>0) {
  245. stack[i] += stack[i-1];
  246. }
  247. tot += data[i][1];
  248. }
  249. var fact = Math.PI*2/stack[stack.length - 1];
  250. for (var i=0; i<stack.length; i++) {
  251. td[i][1] = stack[i] * fact;
  252. td[i][2] = data[i][1]/tot;
  253. }
  254. return td;
  255. };
  256. $.jqplot.DonutRenderer.prototype.drawSlice = function (ctx, ang1, ang2, color, isShadow) {
  257. var r = this._diameter / 2;
  258. var ri = r - this._thickness;
  259. var fill = this.fill;
  260. // var lineWidth = this.lineWidth;
  261. ctx.save();
  262. ctx.translate(this._center[0], this._center[1]);
  263. // ctx.translate(this.sliceMargin*Math.cos((ang1+ang2)/2), this.sliceMargin*Math.sin((ang1+ang2)/2));
  264. if (isShadow) {
  265. for (var i=0; i<this.shadowDepth; i++) {
  266. ctx.save();
  267. ctx.translate(this.shadowOffset*Math.cos(this.shadowAngle/180*Math.PI), this.shadowOffset*Math.sin(this.shadowAngle/180*Math.PI));
  268. doDraw();
  269. }
  270. }
  271. else {
  272. doDraw();
  273. }
  274. function doDraw () {
  275. // Fix for IE and Chrome that can't seem to draw circles correctly.
  276. // ang2 should always be <= 2 pi since that is the way the data is converted.
  277. if (ang2 > 6.282 + this.startAngle) {
  278. ang2 = 6.282 + this.startAngle;
  279. if (ang1 > ang2) {
  280. ang1 = 6.281 + this.startAngle;
  281. }
  282. }
  283. // Fix for IE, where it can't seem to handle 0 degree angles. Also avoids
  284. // ugly line on unfilled donuts.
  285. if (ang1 >= ang2) {
  286. return;
  287. }
  288. ctx.beginPath();
  289. ctx.fillStyle = color;
  290. ctx.strokeStyle = color;
  291. // ctx.lineWidth = lineWidth;
  292. ctx.arc(0, 0, r, ang1, ang2, false);
  293. ctx.lineTo(ri*Math.cos(ang2), ri*Math.sin(ang2));
  294. ctx.arc(0,0, ri, ang2, ang1, true);
  295. ctx.closePath();
  296. if (fill) {
  297. ctx.fill();
  298. }
  299. else {
  300. ctx.stroke();
  301. }
  302. }
  303. if (isShadow) {
  304. for (var i=0; i<this.shadowDepth; i++) {
  305. ctx.restore();
  306. }
  307. }
  308. ctx.restore();
  309. };
  310. // called with scope of series
  311. $.jqplot.DonutRenderer.prototype.draw = function (ctx, gd, options, plot) {
  312. var i;
  313. var opts = (options != undefined) ? options : {};
  314. // offset and direction of offset due to legend placement
  315. var offx = 0;
  316. var offy = 0;
  317. var trans = 1;
  318. // var colorGenerator = new this.colorGenerator(this.seriesColors);
  319. if (options.legendInfo && options.legendInfo.placement == 'insideGrid') {
  320. var li = options.legendInfo;
  321. switch (li.location) {
  322. case 'nw':
  323. offx = li.width + li.xoffset;
  324. break;
  325. case 'w':
  326. offx = li.width + li.xoffset;
  327. break;
  328. case 'sw':
  329. offx = li.width + li.xoffset;
  330. break;
  331. case 'ne':
  332. offx = li.width + li.xoffset;
  333. trans = -1;
  334. break;
  335. case 'e':
  336. offx = li.width + li.xoffset;
  337. trans = -1;
  338. break;
  339. case 'se':
  340. offx = li.width + li.xoffset;
  341. trans = -1;
  342. break;
  343. case 'n':
  344. offy = li.height + li.yoffset;
  345. break;
  346. case 's':
  347. offy = li.height + li.yoffset;
  348. trans = -1;
  349. break;
  350. default:
  351. break;
  352. }
  353. }
  354. var shadow = (opts.shadow != undefined) ? opts.shadow : this.shadow;
  355. var showLine = (opts.showLine != undefined) ? opts.showLine : this.showLine;
  356. var fill = (opts.fill != undefined) ? opts.fill : this.fill;
  357. var cw = ctx.canvas.width;
  358. var ch = ctx.canvas.height;
  359. var w = cw - offx - 2 * this.padding;
  360. var h = ch - offy - 2 * this.padding;
  361. var mindim = Math.min(w,h);
  362. var d = mindim;
  363. var ringmargin = (this.ringMargin == null) ? this.sliceMargin * 2.0 : this.ringMargin;
  364. for (var i=0; i<this._previousSeries.length; i++) {
  365. d -= 2.0 * this._previousSeries[i]._thickness + 2.0 * ringmargin;
  366. }
  367. this._diameter = this.diameter || d;
  368. if (this.innerDiameter != null) {
  369. var od = (this._numberSeries > 1 && this.index > 0) ? this._previousSeries[0]._diameter : this._diameter;
  370. this._thickness = this.thickness || (od - this.innerDiameter - 2.0*ringmargin*this._numberSeries) / this._numberSeries/2.0;
  371. }
  372. else {
  373. this._thickness = this.thickness || mindim / 2 / (this._numberSeries + 1) * 0.85;
  374. }
  375. var r = this._radius = this._diameter/2;
  376. this._innerRadius = this._radius - this._thickness;
  377. var sa = this.startAngle / 180 * Math.PI;
  378. this._center = [(cw - trans * offx)/2 + trans * offx, (ch - trans*offy)/2 + trans * offy];
  379. if (this.shadow) {
  380. var shadowColor = 'rgba(0,0,0,'+this.shadowAlpha+')';
  381. for (var i=0; i<gd.length; i++) {
  382. var ang1 = (i == 0) ? sa : gd[i-1][1] + sa;
  383. // Adjust ang1 and ang2 for sliceMargin
  384. ang1 += this.sliceMargin/180*Math.PI;
  385. this.renderer.drawSlice.call (this, ctx, ang1, gd[i][1]+sa, shadowColor, true);
  386. }
  387. }
  388. for (var i=0; i<gd.length; i++) {
  389. var ang1 = (i == 0) ? sa : gd[i-1][1] + sa;
  390. // Adjust ang1 and ang2 for sliceMargin
  391. ang1 += this.sliceMargin/180*Math.PI;
  392. var ang2 = gd[i][1] + sa;
  393. this._sliceAngles.push([ang1, ang2]);
  394. this.renderer.drawSlice.call (this, ctx, ang1, ang2, this.seriesColors[i], false);
  395. if (this.showDataLabels && gd[i][2]*100 >= this.dataLabelThreshold) {
  396. var fstr, avgang = (ang1+ang2)/2, label;
  397. if (this.dataLabels == 'label') {
  398. fstr = this.dataLabelFormatString || '%s';
  399. label = $.jqplot.sprintf(fstr, gd[i][0]);
  400. }
  401. else if (this.dataLabels == 'value') {
  402. fstr = this.dataLabelFormatString || '%d';
  403. label = $.jqplot.sprintf(fstr, this.data[i][1]);
  404. }
  405. else if (this.dataLabels == 'percent') {
  406. fstr = this.dataLabelFormatString || '%d%%';
  407. label = $.jqplot.sprintf(fstr, gd[i][2]*100);
  408. }
  409. else if (this.dataLabels.constructor == Array) {
  410. fstr = this.dataLabelFormatString || '%s';
  411. label = $.jqplot.sprintf(fstr, this.dataLabels[i]);
  412. }
  413. var fact = this._innerRadius + this._thickness * this.dataLabelPositionFactor + this.sliceMargin + this.dataLabelNudge;
  414. var x = this._center[0] + Math.cos(avgang) * fact + this.canvas._offsets.left;
  415. var y = this._center[1] + Math.sin(avgang) * fact + this.canvas._offsets.top;
  416. var labelelem = $('<span class="jqplot-donut-series jqplot-data-label" style="position:absolute;">' + label + '</span>').insertBefore(plot.eventCanvas._elem);
  417. x -= labelelem.width()/2;
  418. y -= labelelem.height()/2;
  419. x = Math.round(x);
  420. y = Math.round(y);
  421. labelelem.css({left: x, top: y});
  422. }
  423. }
  424. };
  425. $.jqplot.DonutAxisRenderer = function() {
  426. $.jqplot.LinearAxisRenderer.call(this);
  427. };
  428. $.jqplot.DonutAxisRenderer.prototype = new $.jqplot.LinearAxisRenderer();
  429. $.jqplot.DonutAxisRenderer.prototype.constructor = $.jqplot.DonutAxisRenderer;
  430. // There are no traditional axes on a donut chart. We just need to provide
  431. // dummy objects with properties so the plot will render.
  432. // called with scope of axis object.
  433. $.jqplot.DonutAxisRenderer.prototype.init = function(options){
  434. //
  435. this.tickRenderer = $.jqplot.DonutTickRenderer;
  436. $.extend(true, this, options);
  437. // I don't think I'm going to need _dataBounds here.
  438. // have to go Axis scaling in a way to fit chart onto plot area
  439. // and provide u2p and p2u functionality for mouse cursor, etc.
  440. // for convienence set _dataBounds to 0 and 100 and
  441. // set min/max to 0 and 100.
  442. this._dataBounds = {min:0, max:100};
  443. this.min = 0;
  444. this.max = 100;
  445. this.showTicks = false;
  446. this.ticks = [];
  447. this.showMark = false;
  448. this.show = false;
  449. };
  450. $.jqplot.DonutLegendRenderer = function(){
  451. $.jqplot.TableLegendRenderer.call(this);
  452. };
  453. $.jqplot.DonutLegendRenderer.prototype = new $.jqplot.TableLegendRenderer();
  454. $.jqplot.DonutLegendRenderer.prototype.constructor = $.jqplot.DonutLegendRenderer;
  455. /**
  456. * Class: $.jqplot.DonutLegendRenderer
  457. * Legend Renderer specific to donut plots. Set by default
  458. * when user creates a donut plot.
  459. */
  460. $.jqplot.DonutLegendRenderer.prototype.init = function(options) {
  461. // Group: Properties
  462. //
  463. // prop: numberRows
  464. // Maximum number of rows in the legend. 0 or null for unlimited.
  465. this.numberRows = null;
  466. // prop: numberColumns
  467. // Maximum number of columns in the legend. 0 or null for unlimited.
  468. this.numberColumns = null;
  469. $.extend(true, this, options);
  470. };
  471. // called with context of legend
  472. $.jqplot.DonutLegendRenderer.prototype.draw = function() {
  473. var legend = this;
  474. if (this.show) {
  475. var series = this._series;
  476. var ss = 'position:absolute;';
  477. ss += (this.background) ? 'background:'+this.background+';' : '';
  478. ss += (this.border) ? 'border:'+this.border+';' : '';
  479. ss += (this.fontSize) ? 'font-size:'+this.fontSize+';' : '';
  480. ss += (this.fontFamily) ? 'font-family:'+this.fontFamily+';' : '';
  481. ss += (this.textColor) ? 'color:'+this.textColor+';' : '';
  482. ss += (this.marginTop != null) ? 'margin-top:'+this.marginTop+';' : '';
  483. ss += (this.marginBottom != null) ? 'margin-bottom:'+this.marginBottom+';' : '';
  484. ss += (this.marginLeft != null) ? 'margin-left:'+this.marginLeft+';' : '';
  485. ss += (this.marginRight != null) ? 'margin-right:'+this.marginRight+';' : '';
  486. this._elem = $('<table class="jqplot-table-legend" style="'+ss+'"></table>');
  487. // Donut charts legends don't go by number of series, but by number of data points
  488. // in the series. Refactor things here for that.
  489. var pad = false,
  490. reverse = false,
  491. nr, nc;
  492. var s = series[0];
  493. var colorGenerator = new $.jqplot.ColorGenerator(s.seriesColors);
  494. if (s.show) {
  495. var pd = s.data;
  496. if (this.numberRows) {
  497. nr = this.numberRows;
  498. if (!this.numberColumns){
  499. nc = Math.ceil(pd.length/nr);
  500. }
  501. else{
  502. nc = this.numberColumns;
  503. }
  504. }
  505. else if (this.numberColumns) {
  506. nc = this.numberColumns;
  507. nr = Math.ceil(pd.length/this.numberColumns);
  508. }
  509. else {
  510. nr = pd.length;
  511. nc = 1;
  512. }
  513. var i, j, tr, td1, td2, lt, rs, color;
  514. var idx = 0;
  515. for (i=0; i<nr; i++) {
  516. if (reverse){
  517. tr = $('<tr class="jqplot-table-legend"></tr>').prependTo(this._elem);
  518. }
  519. else{
  520. tr = $('<tr class="jqplot-table-legend"></tr>').appendTo(this._elem);
  521. }
  522. for (j=0; j<nc; j++) {
  523. if (idx < pd.length){
  524. lt = this.labels[idx] || pd[idx][0].toString();
  525. color = colorGenerator.next();
  526. if (!reverse){
  527. if (i>0){
  528. pad = true;
  529. }
  530. else{
  531. pad = false;
  532. }
  533. }
  534. else{
  535. if (i == nr -1){
  536. pad = false;
  537. }
  538. else{
  539. pad = true;
  540. }
  541. }
  542. rs = (pad) ? this.rowSpacing : '0';
  543. td1 = $('<td class="jqplot-table-legend" style="text-align:center;padding-top:'+rs+';">'+
  544. '<div><div class="jqplot-table-legend-swatch" style="border-color:'+color+';"></div>'+
  545. '</div></td>');
  546. td2 = $('<td class="jqplot-table-legend" style="padding-top:'+rs+';"></td>');
  547. if (this.escapeHtml){
  548. td2.text(lt);
  549. }
  550. else {
  551. td2.html(lt);
  552. }
  553. if (reverse) {
  554. td2.prependTo(tr);
  555. td1.prependTo(tr);
  556. }
  557. else {
  558. td1.appendTo(tr);
  559. td2.appendTo(tr);
  560. }
  561. pad = true;
  562. }
  563. idx++;
  564. }
  565. }
  566. }
  567. }
  568. return this._elem;
  569. };
  570. // setup default renderers for axes and legend so user doesn't have to
  571. // called with scope of plot
  572. function preInit(target, data, options) {
  573. options = options || {};
  574. options.axesDefaults = options.axesDefaults || {};
  575. options.legend = options.legend || {};
  576. options.seriesDefaults = options.seriesDefaults || {};
  577. // only set these if there is a donut series
  578. var setopts = false;
  579. if (options.seriesDefaults.renderer == $.jqplot.DonutRenderer) {
  580. setopts = true;
  581. }
  582. else if (options.series) {
  583. for (var i=0; i < options.series.length; i++) {
  584. if (options.series[i].renderer == $.jqplot.DonutRenderer) {
  585. setopts = true;
  586. }
  587. }
  588. }
  589. if (setopts) {
  590. options.axesDefaults.renderer = $.jqplot.DonutAxisRenderer;
  591. options.legend.renderer = $.jqplot.DonutLegendRenderer;
  592. options.legend.preDraw = true;
  593. options.seriesDefaults.pointLabels = {show: false};
  594. }
  595. }
  596. // called with scope of plot.
  597. function postInit(target, data, options) {
  598. // if multiple series, add a reference to the previous one so that
  599. // donut rings can nest.
  600. for (var i=1; i<this.series.length; i++) {
  601. if (!this.series[i]._previousSeries.length){
  602. for (var j=0; j<i; j++) {
  603. if (this.series[i].renderer.constructor == $.jqplot.DonutRenderer && this.series[j].renderer.constructor == $.jqplot.DonutRenderer) {
  604. this.series[i]._previousSeries.push(this.series[j]);
  605. }
  606. }
  607. }
  608. }
  609. for (i=0; i<this.series.length; i++) {
  610. if (this.series[i].renderer.constructor == $.jqplot.DonutRenderer) {
  611. this.series[i]._numberSeries = this.series.length;
  612. // don't allow mouseover and mousedown at same time.
  613. if (this.series[i].highlightMouseOver) {
  614. this.series[i].highlightMouseDown = false;
  615. }
  616. }
  617. }
  618. }
  619. var postParseOptionsRun = false;
  620. // called with scope of plot
  621. function postParseOptions(options) {
  622. for (var i=0; i<this.series.length; i++) {
  623. this.series[i].seriesColors = this.seriesColors;
  624. this.series[i].colorGenerator = $.jqplot.colorGenerator;
  625. }
  626. }
  627. function highlight (plot, sidx, pidx) {
  628. var s = plot.series[sidx];
  629. var canvas = plot.plugins.donutRenderer.highlightCanvas;
  630. canvas._ctx.clearRect(0,0,canvas._ctx.canvas.width, canvas._ctx.canvas.height);
  631. s._highlightedPoint = pidx;
  632. plot.plugins.donutRenderer.highlightedSeriesIndex = sidx;
  633. s.renderer.drawSlice.call(s, canvas._ctx, s._sliceAngles[pidx][0], s._sliceAngles[pidx][1], s.highlightColors[pidx], false);
  634. }
  635. function unhighlight (plot) {
  636. var canvas = plot.plugins.donutRenderer.highlightCanvas;
  637. canvas._ctx.clearRect(0,0, canvas._ctx.canvas.width, canvas._ctx.canvas.height);
  638. for (var i=0; i<plot.series.length; i++) {
  639. plot.series[i]._highlightedPoint = null;
  640. }
  641. plot.plugins.donutRenderer.highlightedSeriesIndex = null;
  642. plot.target.trigger('jqplotDataUnhighlight');
  643. }
  644. function handleMove(ev, gridpos, datapos, neighbor, plot) {
  645. if (neighbor) {
  646. var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
  647. var evt1 = jQuery.Event('jqplotDataMouseOver');
  648. evt1.pageX = ev.pageX;
  649. evt1.pageY = ev.pageY;
  650. plot.target.trigger(evt1, ins);
  651. if (plot.series[ins[0]].highlightMouseOver && !(ins[0] == plot.plugins.donutRenderer.highlightedSeriesIndex && ins[1] == plot.series[ins[0]]._highlightedPoint)) {
  652. var evt = jQuery.Event('jqplotDataHighlight');
  653. evt.which = ev.which;
  654. evt.pageX = ev.pageX;
  655. evt.pageY = ev.pageY;
  656. plot.target.trigger(evt, ins);
  657. highlight (plot, ins[0], ins[1]);
  658. }
  659. }
  660. else if (neighbor == null) {
  661. unhighlight (plot);
  662. }
  663. }
  664. function handleMouseDown(ev, gridpos, datapos, neighbor, plot) {
  665. if (neighbor) {
  666. var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
  667. if (plot.series[ins[0]].highlightMouseDown && !(ins[0] == plot.plugins.donutRenderer.highlightedSeriesIndex && ins[1] == plot.series[ins[0]]._highlightedPoint)) {
  668. var evt = jQuery.Event('jqplotDataHighlight');
  669. evt.which = ev.which;
  670. evt.pageX = ev.pageX;
  671. evt.pageY = ev.pageY;
  672. plot.target.trigger(evt, ins);
  673. highlight (plot, ins[0], ins[1]);
  674. }
  675. }
  676. else if (neighbor == null) {
  677. unhighlight (plot);
  678. }
  679. }
  680. function handleMouseUp(ev, gridpos, datapos, neighbor, plot) {
  681. var idx = plot.plugins.donutRenderer.highlightedSeriesIndex;
  682. if (idx != null && plot.series[idx].highlightMouseDown) {
  683. unhighlight(plot);
  684. }
  685. }
  686. function handleClick(ev, gridpos, datapos, neighbor, plot) {
  687. if (neighbor) {
  688. var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
  689. var evt = jQuery.Event('jqplotDataClick');
  690. evt.which = ev.which;
  691. evt.pageX = ev.pageX;
  692. evt.pageY = ev.pageY;
  693. plot.target.trigger(evt, ins);
  694. }
  695. }
  696. function handleRightClick(ev, gridpos, datapos, neighbor, plot) {
  697. if (neighbor) {
  698. var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
  699. var idx = plot.plugins.donutRenderer.highlightedSeriesIndex;
  700. if (idx != null && plot.series[idx].highlightMouseDown) {
  701. unhighlight(plot);
  702. }
  703. var evt = jQuery.Event('jqplotDataRightClick');
  704. evt.which = ev.which;
  705. evt.pageX = ev.pageX;
  706. evt.pageY = ev.pageY;
  707. plot.target.trigger(evt, ins);
  708. }
  709. }
  710. // called within context of plot
  711. // create a canvas which we can draw on.
  712. // insert it before the eventCanvas, so eventCanvas will still capture events.
  713. function postPlotDraw() {
  714. // Memory Leaks patch
  715. if (this.plugins.donutRenderer && this.plugins.donutRenderer.highlightCanvas) {
  716. this.plugins.donutRenderer.highlightCanvas.resetCanvas();
  717. this.plugins.donutRenderer.highlightCanvas = null;
  718. }
  719. this.plugins.donutRenderer = {highlightedSeriesIndex:null};
  720. this.plugins.donutRenderer.highlightCanvas = new $.jqplot.GenericCanvas();
  721. // do we have any data labels? if so, put highlight canvas before those
  722. // Fix for broken jquery :first selector with canvas (VML) elements.
  723. var labels = $(this.targetId+' .jqplot-data-label');
  724. if (labels.length) {
  725. $(labels[0]).before(this.plugins.donutRenderer.highlightCanvas.createElement(this._gridPadding, 'jqplot-donutRenderer-highlight-canvas', this._plotDimensions, this));
  726. }
  727. // else put highlight canvas before event canvas.
  728. else {
  729. this.eventCanvas._elem.before(this.plugins.donutRenderer.highlightCanvas.createElement(this._gridPadding, 'jqplot-donutRenderer-highlight-canvas', this._plotDimensions, this));
  730. }
  731. var hctx = this.plugins.donutRenderer.highlightCanvas.setContext();
  732. this.eventCanvas._elem.bind('mouseleave', {plot:this}, function (ev) { unhighlight(ev.data.plot); });
  733. }
  734. $.jqplot.preInitHooks.push(preInit);
  735. $.jqplot.DonutTickRenderer = function() {
  736. $.jqplot.AxisTickRenderer.call(this);
  737. };
  738. $.jqplot.DonutTickRenderer.prototype = new $.jqplot.AxisTickRenderer();
  739. $.jqplot.DonutTickRenderer.prototype.constructor = $.jqplot.DonutTickRenderer;
  740. })(jQuery);