jqplot.donutRenderer.js 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816
  1. /**
  2. * jqPlot
  3. * Pure JavaScript plotting plugin using jQuery
  4. *
  5. * Version: 1.0.9
  6. * Revision: d96a669
  7. *
  8. * Copyright (c) 2009-2016 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: totalLabel
  134. // true to show total label in the centre
  135. this.totalLabel = false;
  136. // prop: dataLabelFormatString
  137. // Format string for data labels. If none, '%s' is used for "label" and for arrays, '%d' for value and '%d%%' for percentage.
  138. this.dataLabelFormatString = null;
  139. // prop: dataLabelThreshold
  140. // Threshhold in percentage (0 - 100) of pie area, below which no label will be displayed.
  141. // This applies to all label types, not just to percentage labels.
  142. this.dataLabelThreshold = 3;
  143. // prop: dataLabelPositionFactor
  144. // A Multiplier (0-1) of the pie radius which controls position of label on slice.
  145. // Increasing will slide label toward edge of pie, decreasing will slide label toward center of pie.
  146. this.dataLabelPositionFactor = 0.4;
  147. // prop: dataLabelNudge
  148. // Number of pixels to slide the label away from (+) or toward (-) the center of the pie.
  149. this.dataLabelNudge = 0;
  150. // prop: startAngle
  151. // Angle to start drawing donut in degrees.
  152. // According to orientation of canvas coordinate system:
  153. // 0 = on the positive x axis
  154. // -90 = on the positive y axis.
  155. // 90 = on the negaive y axis.
  156. // 180 or - 180 = on the negative x axis.
  157. this.startAngle = 0;
  158. this.tickRenderer = $.jqplot.DonutTickRenderer;
  159. // Used as check for conditions where donut shouldn't be drawn.
  160. this._drawData = true;
  161. this._type = 'donut';
  162. // if user has passed in highlightMouseDown option and not set highlightMouseOver, disable highlightMouseOver
  163. if (options.highlightMouseDown && options.highlightMouseOver == null) {
  164. options.highlightMouseOver = false;
  165. }
  166. $.extend(true, this, options);
  167. if (this.diameter != null) {
  168. this.diameter = this.diameter - this.sliceMargin;
  169. }
  170. this._diameter = null;
  171. this._innerDiameter = null;
  172. this._radius = null;
  173. this._innerRadius = null;
  174. this._thickness = null;
  175. // references to the previous series in the plot to properly calculate diameters
  176. // and thicknesses of nested rings.
  177. this._previousSeries = [];
  178. this._numberSeries = 1;
  179. // array of [start,end] angles arrays, one for each slice. In radians.
  180. this._sliceAngles = [];
  181. // index of the currenty highlighted point, if any
  182. this._highlightedPoint = null;
  183. // set highlight colors if none provided
  184. if (this.highlightColors.length == 0) {
  185. for (var i=0; i<this.seriesColors.length; i++){
  186. var rgba = $.jqplot.getColorComponents(this.seriesColors[i]);
  187. var newrgb = [rgba[0], rgba[1], rgba[2]];
  188. var sum = newrgb[0] + newrgb[1] + newrgb[2];
  189. for (var j=0; j<3; j++) {
  190. // when darkening, lowest color component can be is 60.
  191. newrgb[j] = (sum > 570) ? newrgb[j] * 0.8 : newrgb[j] + 0.3 * (255 - newrgb[j]);
  192. newrgb[j] = parseInt(newrgb[j], 10);
  193. }
  194. this.highlightColors.push('rgb('+newrgb[0]+','+newrgb[1]+','+newrgb[2]+')');
  195. }
  196. }
  197. plot.postParseOptionsHooks.addOnce(postParseOptions);
  198. plot.postInitHooks.addOnce(postInit);
  199. plot.eventListenerHooks.addOnce('jqplotMouseMove', handleMove);
  200. plot.eventListenerHooks.addOnce('jqplotMouseDown', handleMouseDown);
  201. plot.eventListenerHooks.addOnce('jqplotMouseUp', handleMouseUp);
  202. plot.eventListenerHooks.addOnce('jqplotClick', handleClick);
  203. plot.eventListenerHooks.addOnce('jqplotRightClick', handleRightClick);
  204. plot.postDrawHooks.addOnce(postPlotDraw);
  205. };
  206. $.jqplot.DonutRenderer.prototype.setGridData = function(plot) {
  207. // set gridData property. This will hold angle in radians of each data point.
  208. var stack = [];
  209. var td = [];
  210. var sa = this.startAngle/180*Math.PI;
  211. var tot = 0;
  212. // don't know if we have any valid data yet, so set plot to not draw.
  213. this._drawData = false;
  214. for (var i=0; i<this.data.length; i++){
  215. if (this.data[i][1] != 0) {
  216. // we have data, O.K. to draw.
  217. this._drawData = true;
  218. }
  219. stack.push(this.data[i][1]);
  220. td.push([this.data[i][0]]);
  221. if (i>0) {
  222. stack[i] += stack[i-1];
  223. }
  224. tot += this.data[i][1];
  225. }
  226. var fact = Math.PI*2/stack[stack.length - 1];
  227. for (var i=0; i<stack.length; i++) {
  228. td[i][1] = stack[i] * fact;
  229. td[i][2] = this.data[i][1]/tot;
  230. }
  231. this.gridData = td;
  232. };
  233. $.jqplot.DonutRenderer.prototype.makeGridData = function(data, plot) {
  234. var stack = [];
  235. var td = [];
  236. var tot = 0;
  237. var sa = this.startAngle/180*Math.PI;
  238. // don't know if we have any valid data yet, so set plot to not draw.
  239. this._drawData = false;
  240. for (var i=0; i<data.length; i++){
  241. if (this.data[i][1] != 0) {
  242. // we have data, O.K. to draw.
  243. this._drawData = true;
  244. }
  245. stack.push(data[i][1]);
  246. td.push([data[i][0]]);
  247. if (i>0) {
  248. stack[i] += stack[i-1];
  249. }
  250. tot += data[i][1];
  251. }
  252. var fact = Math.PI*2/stack[stack.length - 1];
  253. for (var i=0; i<stack.length; i++) {
  254. td[i][1] = stack[i] * fact;
  255. td[i][2] = data[i][1]/tot;
  256. }
  257. this._totalAmount = tot;
  258. return td;
  259. };
  260. $.jqplot.DonutRenderer.prototype.drawSlice = function (ctx, ang1, ang2, color, isShadow) {
  261. var r = this._diameter / 2;
  262. var ri = r - this._thickness;
  263. var fill = this.fill;
  264. // var lineWidth = this.lineWidth;
  265. ctx.save();
  266. ctx.translate(this._center[0], this._center[1]);
  267. // ctx.translate(this.sliceMargin*Math.cos((ang1+ang2)/2), this.sliceMargin*Math.sin((ang1+ang2)/2));
  268. if (isShadow) {
  269. for (var i=0; i<this.shadowDepth; i++) {
  270. ctx.save();
  271. ctx.translate(this.shadowOffset*Math.cos(this.shadowAngle/180*Math.PI), this.shadowOffset*Math.sin(this.shadowAngle/180*Math.PI));
  272. doDraw();
  273. }
  274. }
  275. else {
  276. doDraw();
  277. }
  278. function doDraw () {
  279. // Fix for IE and Chrome that can't seem to draw circles correctly.
  280. // ang2 should always be <= 2 pi since that is the way the data is converted.
  281. if (ang2 > 6.282 + this.startAngle) {
  282. ang2 = 6.282 + this.startAngle;
  283. if (ang1 > ang2) {
  284. ang1 = 6.281 + this.startAngle;
  285. }
  286. }
  287. // Fix for IE, where it can't seem to handle 0 degree angles. Also avoids
  288. // ugly line on unfilled donuts.
  289. if (ang1 >= ang2) {
  290. return;
  291. }
  292. ctx.beginPath();
  293. ctx.fillStyle = color;
  294. ctx.strokeStyle = color;
  295. // ctx.lineWidth = lineWidth;
  296. ctx.arc(0, 0, r, ang1, ang2, false);
  297. ctx.lineTo(ri*Math.cos(ang2), ri*Math.sin(ang2));
  298. ctx.arc(0,0, ri, ang2, ang1, true);
  299. ctx.closePath();
  300. if (fill) {
  301. ctx.fill();
  302. }
  303. else {
  304. ctx.stroke();
  305. }
  306. }
  307. if (isShadow) {
  308. for (var i=0; i<this.shadowDepth; i++) {
  309. ctx.restore();
  310. }
  311. }
  312. ctx.restore();
  313. };
  314. // called with scope of series
  315. $.jqplot.DonutRenderer.prototype.draw = function (ctx, gd, options, plot) {
  316. var i;
  317. var opts = (options != undefined) ? options : {};
  318. // offset and direction of offset due to legend placement
  319. var offx = 0;
  320. var offy = 0;
  321. var trans = 1;
  322. // var colorGenerator = new this.colorGenerator(this.seriesColors);
  323. if (options.legendInfo && options.legendInfo.placement == 'insideGrid') {
  324. var li = options.legendInfo;
  325. switch (li.location) {
  326. case 'nw':
  327. offx = li.width + li.xoffset;
  328. break;
  329. case 'w':
  330. offx = li.width + li.xoffset;
  331. break;
  332. case 'sw':
  333. offx = li.width + li.xoffset;
  334. break;
  335. case 'ne':
  336. offx = li.width + li.xoffset;
  337. trans = -1;
  338. break;
  339. case 'e':
  340. offx = li.width + li.xoffset;
  341. trans = -1;
  342. break;
  343. case 'se':
  344. offx = li.width + li.xoffset;
  345. trans = -1;
  346. break;
  347. case 'n':
  348. offy = li.height + li.yoffset;
  349. break;
  350. case 's':
  351. offy = li.height + li.yoffset;
  352. trans = -1;
  353. break;
  354. default:
  355. break;
  356. }
  357. }
  358. var shadow = (opts.shadow != undefined) ? opts.shadow : this.shadow;
  359. var showLine = (opts.showLine != undefined) ? opts.showLine : this.showLine;
  360. var fill = (opts.fill != undefined) ? opts.fill : this.fill;
  361. //see http://stackoverflow.com/questions/20221461/hidpi-retina-plot-drawing
  362. var cw = parseInt(ctx.canvas.style.width);
  363. var ch = parseInt(ctx.canvas.style.height);
  364. var w = cw - offx - 2 * this.padding;
  365. var h = ch - offy - 2 * this.padding;
  366. var mindim = Math.min(w,h);
  367. var d = mindim;
  368. var ringmargin = (this.ringMargin == null) ? this.sliceMargin * 2.0 : this.ringMargin;
  369. for (var i=0; i<this._previousSeries.length; i++) {
  370. d -= 2.0 * this._previousSeries[i]._thickness + 2.0 * ringmargin;
  371. }
  372. this._diameter = this.diameter || d;
  373. if (this.innerDiameter != null) {
  374. var od = (this._numberSeries > 1 && this.index > 0) ? this._previousSeries[0]._diameter : this._diameter;
  375. this._thickness = this.thickness || (od - this.innerDiameter - 2.0*ringmargin*this._numberSeries) / this._numberSeries/2.0;
  376. }
  377. else {
  378. this._thickness = this.thickness || mindim / 2 / (this._numberSeries + 1) * 0.85;
  379. }
  380. if (this._diameter < 6) {
  381. $.jqplot.log("Diameter of donut too small, not rendering.");
  382. return;
  383. }
  384. var r = this._radius = this._diameter/2;
  385. this._innerRadius = this._radius - this._thickness;
  386. var sa = this.startAngle / 180 * Math.PI;
  387. this._center = [(cw - trans * offx)/2 + trans * offx, (ch - trans*offy)/2 + trans * offy];
  388. if (this.shadow) {
  389. var shadowColor = 'rgba(0,0,0,'+this.shadowAlpha+')';
  390. for (var i=0; i<gd.length; i++) {
  391. var ang1 = (i == 0) ? sa : gd[i-1][1] + sa;
  392. // Adjust ang1 and ang2 for sliceMargin
  393. ang1 += this.sliceMargin/180*Math.PI;
  394. this.renderer.drawSlice.call (this, ctx, ang1, gd[i][1]+sa, shadowColor, true);
  395. }
  396. }
  397. for (var i=0; i<gd.length; i++) {
  398. var ang1 = (i == 0) ? sa : gd[i-1][1] + sa;
  399. // Adjust ang1 and ang2 for sliceMargin
  400. ang1 += this.sliceMargin/180*Math.PI;
  401. var ang2 = gd[i][1] + sa;
  402. this._sliceAngles.push([ang1, ang2]);
  403. this.renderer.drawSlice.call (this, ctx, ang1, ang2, this.seriesColors[i], false);
  404. if (this.showDataLabels && gd[i][2]*100 >= this.dataLabelThreshold) {
  405. var fstr, avgang = (ang1+ang2)/2, label;
  406. if (this.dataLabels == 'label') {
  407. fstr = this.dataLabelFormatString || '%s';
  408. label = $.jqplot.sprintf(fstr, gd[i][0]);
  409. }
  410. else if (this.dataLabels == 'value') {
  411. fstr = this.dataLabelFormatString || '%d';
  412. label = $.jqplot.sprintf(fstr, this.data[i][1]);
  413. }
  414. else if (this.dataLabels == 'percent') {
  415. fstr = this.dataLabelFormatString || '%d%%';
  416. label = $.jqplot.sprintf(fstr, gd[i][2]*100);
  417. }
  418. else if (this.dataLabels.constructor == Array) {
  419. fstr = this.dataLabelFormatString || '%s';
  420. label = $.jqplot.sprintf(fstr, this.dataLabels[i]);
  421. }
  422. var fact = this._innerRadius + this._thickness * this.dataLabelPositionFactor + this.sliceMargin + this.dataLabelNudge;
  423. var x = this._center[0] + Math.cos(avgang) * fact + this.canvas._offsets.left;
  424. var y = this._center[1] + Math.sin(avgang) * fact + this.canvas._offsets.top;
  425. var labelelem = $('<span class="jqplot-donut-series jqplot-data-label" style="position:absolute;">' + label + '</span>').insertBefore(plot.eventCanvas._elem);
  426. x -= labelelem.width()/2;
  427. y -= labelelem.height()/2;
  428. x = Math.round(x);
  429. y = Math.round(y);
  430. labelelem.css({left: x, top: y});
  431. }
  432. }
  433. if (this.totalLabel) {
  434. var totalLabel = $('<div class="jqplot-data-label" style="position:absolute">' + this._totalAmount + '</div>').insertAfter(plot.eventCanvas._elem);
  435. totalLabel.css({left: this._center[0], top: this._center[1]});
  436. }
  437. };
  438. $.jqplot.DonutAxisRenderer = function() {
  439. $.jqplot.LinearAxisRenderer.call(this);
  440. };
  441. $.jqplot.DonutAxisRenderer.prototype = new $.jqplot.LinearAxisRenderer();
  442. $.jqplot.DonutAxisRenderer.prototype.constructor = $.jqplot.DonutAxisRenderer;
  443. // There are no traditional axes on a donut chart. We just need to provide
  444. // dummy objects with properties so the plot will render.
  445. // called with scope of axis object.
  446. $.jqplot.DonutAxisRenderer.prototype.init = function(options){
  447. //
  448. this.tickRenderer = $.jqplot.DonutTickRenderer;
  449. $.extend(true, this, options);
  450. // I don't think I'm going to need _dataBounds here.
  451. // have to go Axis scaling in a way to fit chart onto plot area
  452. // and provide u2p and p2u functionality for mouse cursor, etc.
  453. // for convienence set _dataBounds to 0 and 100 and
  454. // set min/max to 0 and 100.
  455. this._dataBounds = {min:0, max:100};
  456. this.min = 0;
  457. this.max = 100;
  458. this.showTicks = false;
  459. this.ticks = [];
  460. this.showMark = false;
  461. this.show = false;
  462. };
  463. $.jqplot.DonutLegendRenderer = function(){
  464. $.jqplot.TableLegendRenderer.call(this);
  465. };
  466. $.jqplot.DonutLegendRenderer.prototype = new $.jqplot.TableLegendRenderer();
  467. $.jqplot.DonutLegendRenderer.prototype.constructor = $.jqplot.DonutLegendRenderer;
  468. /**
  469. * Class: $.jqplot.DonutLegendRenderer
  470. * Legend Renderer specific to donut plots. Set by default
  471. * when user creates a donut plot.
  472. */
  473. $.jqplot.DonutLegendRenderer.prototype.init = function(options) {
  474. // Group: Properties
  475. //
  476. // prop: numberRows
  477. // Maximum number of rows in the legend. 0 or null for unlimited.
  478. this.numberRows = null;
  479. // prop: numberColumns
  480. // Maximum number of columns in the legend. 0 or null for unlimited.
  481. this.numberColumns = null;
  482. $.extend(true, this, options);
  483. };
  484. // called with context of legend
  485. $.jqplot.DonutLegendRenderer.prototype.draw = function() {
  486. var legend = this;
  487. if (this.show) {
  488. var series = this._series;
  489. var ss = 'position:absolute;';
  490. ss += (this.background) ? 'background:'+this.background+';' : '';
  491. ss += (this.border) ? 'border:'+this.border+';' : '';
  492. ss += (this.fontSize) ? 'font-size:'+this.fontSize+';' : '';
  493. ss += (this.fontFamily) ? 'font-family:'+this.fontFamily+';' : '';
  494. ss += (this.textColor) ? 'color:'+this.textColor+';' : '';
  495. ss += (this.marginTop != null) ? 'margin-top:'+this.marginTop+';' : '';
  496. ss += (this.marginBottom != null) ? 'margin-bottom:'+this.marginBottom+';' : '';
  497. ss += (this.marginLeft != null) ? 'margin-left:'+this.marginLeft+';' : '';
  498. ss += (this.marginRight != null) ? 'margin-right:'+this.marginRight+';' : '';
  499. this._elem = $('<table class="jqplot-table-legend" style="'+ss+'"></table>');
  500. // Donut charts legends don't go by number of series, but by number of data points
  501. // in the series. Refactor things here for that.
  502. var pad = false,
  503. reverse = false,
  504. nr, nc;
  505. var s = series[0];
  506. var colorGenerator = new $.jqplot.ColorGenerator(s.seriesColors);
  507. if (s.show) {
  508. var pd = s.data;
  509. if (this.numberRows) {
  510. nr = this.numberRows;
  511. if (!this.numberColumns){
  512. nc = Math.ceil(pd.length/nr);
  513. }
  514. else{
  515. nc = this.numberColumns;
  516. }
  517. }
  518. else if (this.numberColumns) {
  519. nc = this.numberColumns;
  520. nr = Math.ceil(pd.length/this.numberColumns);
  521. }
  522. else {
  523. nr = pd.length;
  524. nc = 1;
  525. }
  526. var i, j, tr, td1, td2, lt, rs, color;
  527. var idx = 0;
  528. for (i=0; i<nr; i++) {
  529. if (reverse){
  530. tr = $('<tr class="jqplot-table-legend"></tr>').prependTo(this._elem);
  531. }
  532. else{
  533. tr = $('<tr class="jqplot-table-legend"></tr>').appendTo(this._elem);
  534. }
  535. for (j=0; j<nc; j++) {
  536. if (idx < pd.length){
  537. lt = this.labels[idx] || pd[idx][0].toString();
  538. color = colorGenerator.next();
  539. if (!reverse){
  540. if (i>0){
  541. pad = true;
  542. }
  543. else{
  544. pad = false;
  545. }
  546. }
  547. else{
  548. if (i == nr -1){
  549. pad = false;
  550. }
  551. else{
  552. pad = true;
  553. }
  554. }
  555. rs = (pad) ? this.rowSpacing : '0';
  556. td1 = $('<td class="jqplot-table-legend" style="text-align:center;padding-top:'+rs+';">'+
  557. '<div><div class="jqplot-table-legend-swatch" style="border-color:'+color+';"></div>'+
  558. '</div></td>');
  559. td2 = $('<td class="jqplot-table-legend" style="padding-top:'+rs+';"></td>');
  560. if (this.escapeHtml){
  561. td2.text(lt);
  562. }
  563. else {
  564. td2.html(lt);
  565. }
  566. if (reverse) {
  567. td2.prependTo(tr);
  568. td1.prependTo(tr);
  569. }
  570. else {
  571. td1.appendTo(tr);
  572. td2.appendTo(tr);
  573. }
  574. pad = true;
  575. }
  576. idx++;
  577. }
  578. }
  579. }
  580. }
  581. return this._elem;
  582. };
  583. // setup default renderers for axes and legend so user doesn't have to
  584. // called with scope of plot
  585. function preInit(target, data, options) {
  586. options = options || {};
  587. options.axesDefaults = options.axesDefaults || {};
  588. options.legend = options.legend || {};
  589. options.seriesDefaults = options.seriesDefaults || {};
  590. // only set these if there is a donut series
  591. var setopts = false;
  592. if (options.seriesDefaults.renderer == $.jqplot.DonutRenderer) {
  593. setopts = true;
  594. }
  595. else if (options.series) {
  596. for (var i=0; i < options.series.length; i++) {
  597. if (options.series[i].renderer == $.jqplot.DonutRenderer) {
  598. setopts = true;
  599. }
  600. }
  601. }
  602. if (setopts) {
  603. options.axesDefaults.renderer = $.jqplot.DonutAxisRenderer;
  604. options.legend.renderer = $.jqplot.DonutLegendRenderer;
  605. options.legend.preDraw = true;
  606. options.seriesDefaults.pointLabels = {show: false};
  607. }
  608. }
  609. // called with scope of plot.
  610. function postInit(target, data, options) {
  611. // if multiple series, add a reference to the previous one so that
  612. // donut rings can nest.
  613. for (var i=1; i<this.series.length; i++) {
  614. if (!this.series[i]._previousSeries.length){
  615. for (var j=0; j<i; j++) {
  616. if (this.series[i].renderer.constructor == $.jqplot.DonutRenderer && this.series[j].renderer.constructor == $.jqplot.DonutRenderer) {
  617. this.series[i]._previousSeries.push(this.series[j]);
  618. }
  619. }
  620. }
  621. }
  622. for (i=0; i<this.series.length; i++) {
  623. if (this.series[i].renderer.constructor == $.jqplot.DonutRenderer) {
  624. this.series[i]._numberSeries = this.series.length;
  625. // don't allow mouseover and mousedown at same time.
  626. if (this.series[i].highlightMouseOver) {
  627. this.series[i].highlightMouseDown = false;
  628. }
  629. }
  630. }
  631. }
  632. var postParseOptionsRun = false;
  633. // called with scope of plot
  634. function postParseOptions(options) {
  635. for (var i=0; i<this.series.length; i++) {
  636. this.series[i].seriesColors = this.seriesColors;
  637. this.series[i].colorGenerator = $.jqplot.colorGenerator;
  638. }
  639. }
  640. function highlight (plot, sidx, pidx) {
  641. var s = plot.series[sidx];
  642. var canvas = plot.plugins.donutRenderer.highlightCanvas;
  643. canvas._ctx.clearRect(0,0,canvas._ctx.canvas.width, canvas._ctx.canvas.height);
  644. s._highlightedPoint = pidx;
  645. plot.plugins.donutRenderer.highlightedSeriesIndex = sidx;
  646. s.renderer.drawSlice.call(s, canvas._ctx, s._sliceAngles[pidx][0], s._sliceAngles[pidx][1], s.highlightColors[pidx], false);
  647. }
  648. function unhighlight (plot) {
  649. var canvas = plot.plugins.donutRenderer.highlightCanvas;
  650. canvas._ctx.clearRect(0,0, canvas._ctx.canvas.width, canvas._ctx.canvas.height);
  651. for (var i=0; i<plot.series.length; i++) {
  652. plot.series[i]._highlightedPoint = null;
  653. }
  654. plot.plugins.donutRenderer.highlightedSeriesIndex = null;
  655. plot.target.trigger('jqplotDataUnhighlight');
  656. }
  657. function handleMove(ev, gridpos, datapos, neighbor, plot) {
  658. if (neighbor) {
  659. var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
  660. var evt1 = jQuery.Event('jqplotDataMouseOver');
  661. evt1.pageX = ev.pageX;
  662. evt1.pageY = ev.pageY;
  663. plot.target.trigger(evt1, ins);
  664. if (plot.series[ins[0]].highlightMouseOver && !(ins[0] == plot.plugins.donutRenderer.highlightedSeriesIndex && ins[1] == plot.series[ins[0]]._highlightedPoint)) {
  665. var evt = jQuery.Event('jqplotDataHighlight');
  666. evt.which = ev.which;
  667. evt.pageX = ev.pageX;
  668. evt.pageY = ev.pageY;
  669. plot.target.trigger(evt, ins);
  670. highlight (plot, ins[0], ins[1]);
  671. }
  672. }
  673. else if (neighbor == null) {
  674. unhighlight (plot);
  675. }
  676. }
  677. function handleMouseDown(ev, gridpos, datapos, neighbor, plot) {
  678. if (neighbor) {
  679. var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
  680. if (plot.series[ins[0]].highlightMouseDown && !(ins[0] == plot.plugins.donutRenderer.highlightedSeriesIndex && ins[1] == plot.series[ins[0]]._highlightedPoint)) {
  681. var evt = jQuery.Event('jqplotDataHighlight');
  682. evt.which = ev.which;
  683. evt.pageX = ev.pageX;
  684. evt.pageY = ev.pageY;
  685. plot.target.trigger(evt, ins);
  686. highlight (plot, ins[0], ins[1]);
  687. }
  688. }
  689. else if (neighbor == null) {
  690. unhighlight (plot);
  691. }
  692. }
  693. function handleMouseUp(ev, gridpos, datapos, neighbor, plot) {
  694. var idx = plot.plugins.donutRenderer.highlightedSeriesIndex;
  695. if (idx != null && plot.series[idx].highlightMouseDown) {
  696. unhighlight(plot);
  697. }
  698. }
  699. function handleClick(ev, gridpos, datapos, neighbor, plot) {
  700. if (neighbor) {
  701. var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
  702. var evt = jQuery.Event('jqplotDataClick');
  703. evt.which = ev.which;
  704. evt.pageX = ev.pageX;
  705. evt.pageY = ev.pageY;
  706. plot.target.trigger(evt, ins);
  707. }
  708. }
  709. function handleRightClick(ev, gridpos, datapos, neighbor, plot) {
  710. if (neighbor) {
  711. var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
  712. var idx = plot.plugins.donutRenderer.highlightedSeriesIndex;
  713. if (idx != null && plot.series[idx].highlightMouseDown) {
  714. unhighlight(plot);
  715. }
  716. var evt = jQuery.Event('jqplotDataRightClick');
  717. evt.which = ev.which;
  718. evt.pageX = ev.pageX;
  719. evt.pageY = ev.pageY;
  720. plot.target.trigger(evt, ins);
  721. }
  722. }
  723. // called within context of plot
  724. // create a canvas which we can draw on.
  725. // insert it before the eventCanvas, so eventCanvas will still capture events.
  726. function postPlotDraw() {
  727. // Memory Leaks patch
  728. if (this.plugins.donutRenderer && this.plugins.donutRenderer.highlightCanvas) {
  729. this.plugins.donutRenderer.highlightCanvas.resetCanvas();
  730. this.plugins.donutRenderer.highlightCanvas = null;
  731. }
  732. this.plugins.donutRenderer = {highlightedSeriesIndex:null};
  733. this.plugins.donutRenderer.highlightCanvas = new $.jqplot.GenericCanvas();
  734. // do we have any data labels? if so, put highlight canvas before those
  735. // Fix for broken jquery :first selector with canvas (VML) elements.
  736. var labels = $(this.targetId+' .jqplot-data-label');
  737. if (labels.length) {
  738. $(labels[0]).before(this.plugins.donutRenderer.highlightCanvas.createElement(this._gridPadding, 'jqplot-donutRenderer-highlight-canvas', this._plotDimensions, this));
  739. }
  740. // else put highlight canvas before event canvas.
  741. else {
  742. this.eventCanvas._elem.before(this.plugins.donutRenderer.highlightCanvas.createElement(this._gridPadding, 'jqplot-donutRenderer-highlight-canvas', this._plotDimensions, this));
  743. }
  744. var hctx = this.plugins.donutRenderer.highlightCanvas.setContext();
  745. this.eventCanvas._elem.bind('mouseleave', {plot:this}, function (ev) { unhighlight(ev.data.plot); });
  746. }
  747. $.jqplot.preInitHooks.push(preInit);
  748. $.jqplot.DonutTickRenderer = function() {
  749. $.jqplot.AxisTickRenderer.call(this);
  750. };
  751. $.jqplot.DonutTickRenderer.prototype = new $.jqplot.AxisTickRenderer();
  752. $.jqplot.DonutTickRenderer.prototype.constructor = $.jqplot.DonutTickRenderer;
  753. })(jQuery);