You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

910 line
39 KiB

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