jqplot.mekkoRenderer.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  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.MekkoRenderer
  34. * Draws a Mekko style chart which shows 3 dimensional data on a 2 dimensional graph.
  35. * the <$.jqplot.MekkoAxisRenderer> should be used with mekko charts. The mekko renderer
  36. * overrides the default legend renderer with its own $.jqplot.MekkoLegendRenderer
  37. * which allows more flexibility to specify number of rows and columns in the legend.
  38. *
  39. * Data is specified per bar in the chart. You can specify data as an array of y values, or as
  40. * an array of [label, value] pairs. Note that labels are used only on the first series.
  41. * Labels on subsequent series are ignored:
  42. *
  43. * > bar1 = [['shirts', 8],['hats', 14],['shoes', 6],['gloves', 16],['dolls', 12]];
  44. * > bar2 = [15,6,9,13,6];
  45. * > bar3 = [['grumpy',4],['sneezy',2],['happy',7],['sleepy',9],['doc',7]];
  46. *
  47. * If you want to place labels for each bar under the axis, you use the barLabels option on
  48. * the axes. The bar labels can be styled with the ".jqplot-mekko-barLabel" css class.
  49. *
  50. * > barLabels = ['Mickey Mouse', 'Donald Duck', 'Goofy'];
  51. * > axes:{xaxis:{barLabels:barLabels}}
  52. *
  53. */
  54. $.jqplot.MekkoRenderer = function(){
  55. this.shapeRenderer = new $.jqplot.ShapeRenderer();
  56. // prop: borderColor
  57. // color of the borders between areas on the chart
  58. this.borderColor = null;
  59. // prop: showBorders
  60. // True to draw borders lines between areas on the chart.
  61. // False will draw borders lines with the same color as the area.
  62. this.showBorders = true;
  63. };
  64. // called with scope of series.
  65. $.jqplot.MekkoRenderer.prototype.init = function(options, plot) {
  66. this.fill = false;
  67. this.fillRect = true;
  68. this.strokeRect = true;
  69. this.shadow = false;
  70. // width of bar on x axis.
  71. this._xwidth = 0;
  72. this._xstart = 0;
  73. $.extend(true, this.renderer, options);
  74. // set the shape renderer options
  75. var opts = {lineJoin:'miter', lineCap:'butt', isarc:false, fillRect:this.fillRect, strokeRect:this.strokeRect};
  76. this.renderer.shapeRenderer.init(opts);
  77. plot.axes.x2axis._series.push(this);
  78. this._type = 'mekko';
  79. };
  80. // Method: setGridData
  81. // converts the user data values to grid coordinates and stores them
  82. // in the gridData array. Will convert user data into appropriate
  83. // rectangles.
  84. // Called with scope of a series.
  85. $.jqplot.MekkoRenderer.prototype.setGridData = function(plot) {
  86. // recalculate the grid data
  87. var xp = this._xaxis.series_u2p;
  88. var yp = this._yaxis.series_u2p;
  89. var data = this._plotData;
  90. this.gridData = [];
  91. // figure out width on x axis.
  92. // this._xwidth = this._sumy / plot._sumy * this.canvas.getWidth();
  93. this._xwidth = xp(this._sumy) - xp(0);
  94. if (this.index>0) {
  95. this._xstart = plot.series[this.index-1]._xstart + plot.series[this.index-1]._xwidth;
  96. }
  97. var totheight = this.canvas.getHeight();
  98. var sumy = 0;
  99. var cury;
  100. var curheight;
  101. for (var i=0; i<data.length; i++) {
  102. if (data[i] != null) {
  103. sumy += data[i][1];
  104. cury = totheight - (sumy / this._sumy * totheight);
  105. curheight = data[i][1] / this._sumy * totheight;
  106. this.gridData.push([this._xstart, cury, this._xwidth, curheight]);
  107. }
  108. }
  109. };
  110. // Method: makeGridData
  111. // converts any arbitrary data values to grid coordinates and
  112. // returns them. This method exists so that plugins can use a series'
  113. // linerenderer to generate grid data points without overwriting the
  114. // grid data associated with that series.
  115. // Called with scope of a series.
  116. $.jqplot.MekkoRenderer.prototype.makeGridData = function(data, plot) {
  117. // recalculate the grid data
  118. // figure out width on x axis.
  119. var xp = this._xaxis.series_u2p;
  120. var totheight = this.canvas.getHeight();
  121. var sumy = 0;
  122. var cury;
  123. var curheight;
  124. var gd = [];
  125. for (var i=0; i<data.length; i++) {
  126. if (data[i] != null) {
  127. sumy += data[i][1];
  128. cury = totheight - (sumy / this._sumy * totheight);
  129. curheight = data[i][1] / this._sumy * totheight;
  130. gd.push([this._xstart, cury, this._xwidth, curheight]);
  131. }
  132. }
  133. return gd;
  134. };
  135. // called within scope of series.
  136. $.jqplot.MekkoRenderer.prototype.draw = function(ctx, gd, options) {
  137. var i;
  138. var opts = (options != undefined) ? options : {};
  139. var showLine = (opts.showLine != undefined) ? opts.showLine : this.showLine;
  140. var colorGenerator = new $.jqplot.ColorGenerator(this.seriesColors);
  141. ctx.save();
  142. if (gd.length) {
  143. if (showLine) {
  144. for (i=0; i<gd.length; i++){
  145. opts.fillStyle = colorGenerator.next();
  146. if (this.renderer.showBorders) {
  147. opts.strokeStyle = this.renderer.borderColor;
  148. }
  149. else {
  150. opts.strokeStyle = opts.fillStyle;
  151. }
  152. this.renderer.shapeRenderer.draw(ctx, gd[i], opts);
  153. }
  154. }
  155. }
  156. ctx.restore();
  157. };
  158. $.jqplot.MekkoRenderer.prototype.drawShadow = function(ctx, gd, options) {
  159. // This is a no-op, no shadows on mekko charts.
  160. };
  161. /**
  162. * Class: $.jqplot.MekkoLegendRenderer
  163. * Legend renderer used by mekko charts with options for
  164. * controlling number or rows and columns as well as placement
  165. * outside of plot area.
  166. *
  167. */
  168. $.jqplot.MekkoLegendRenderer = function(){
  169. //
  170. };
  171. $.jqplot.MekkoLegendRenderer.prototype.init = function(options) {
  172. // prop: numberRows
  173. // Maximum number of rows in the legend. 0 or null for unlimited.
  174. this.numberRows = null;
  175. // prop: numberColumns
  176. // Maximum number of columns in the legend. 0 or null for unlimited.
  177. this.numberColumns = null;
  178. // this will override the placement option on the Legend object
  179. this.placement = "outside";
  180. $.extend(true, this, options);
  181. };
  182. // called with scope of legend
  183. $.jqplot.MekkoLegendRenderer.prototype.draw = function() {
  184. var legend = this;
  185. if (this.show) {
  186. var series = this._series;
  187. var ss = 'position:absolute;';
  188. ss += (this.background) ? 'background:'+this.background+';' : '';
  189. ss += (this.border) ? 'border:'+this.border+';' : '';
  190. ss += (this.fontSize) ? 'font-size:'+this.fontSize+';' : '';
  191. ss += (this.fontFamily) ? 'font-family:'+this.fontFamily+';' : '';
  192. ss += (this.textColor) ? 'color:'+this.textColor+';' : '';
  193. this._elem = $('<table class="jqplot-table-legend" style="'+ss+'"></table>');
  194. // Mekko charts legends don't go by number of series, but by number of data points
  195. // in the series. Refactor things here for that.
  196. var pad = false,
  197. reverse = true, // mekko charts are always stacked, so reverse
  198. nr, nc;
  199. var s = series[0];
  200. var colorGenerator = new $.jqplot.ColorGenerator(s.seriesColors);
  201. if (s.show) {
  202. var pd = s.data;
  203. if (this.numberRows) {
  204. nr = this.numberRows;
  205. if (!this.numberColumns){
  206. nc = Math.ceil(pd.length/nr);
  207. }
  208. else{
  209. nc = this.numberColumns;
  210. }
  211. }
  212. else if (this.numberColumns) {
  213. nc = this.numberColumns;
  214. nr = Math.ceil(pd.length/this.numberColumns);
  215. }
  216. else {
  217. nr = pd.length;
  218. nc = 1;
  219. }
  220. var i, j, tr, td1, td2, lt, rs, color;
  221. var idx = 0;
  222. for (i=0; i<nr; i++) {
  223. if (reverse){
  224. tr = $('<tr class="jqplot-table-legend"></tr>').prependTo(this._elem);
  225. }
  226. else{
  227. tr = $('<tr class="jqplot-table-legend"></tr>').appendTo(this._elem);
  228. }
  229. for (j=0; j<nc; j++) {
  230. if (idx < pd.length) {
  231. lt = this.labels[idx] || pd[idx][0].toString();
  232. color = colorGenerator.next();
  233. if (!reverse){
  234. if (i>0){
  235. pad = true;
  236. }
  237. else{
  238. pad = false;
  239. }
  240. }
  241. else{
  242. if (i == nr -1){
  243. pad = false;
  244. }
  245. else{
  246. pad = true;
  247. }
  248. }
  249. rs = (pad) ? this.rowSpacing : '0';
  250. td1 = $('<td class="jqplot-table-legend" style="text-align:center;padding-top:'+rs+';">'+
  251. '<div><div class="jqplot-table-legend-swatch" style="border-color:'+color+';"></div>'+
  252. '</div></td>');
  253. td2 = $('<td class="jqplot-table-legend" style="padding-top:'+rs+';"></td>');
  254. if (this.escapeHtml){
  255. td2.text(lt);
  256. }
  257. else {
  258. td2.html(lt);
  259. }
  260. if (reverse) {
  261. td2.prependTo(tr);
  262. td1.prependTo(tr);
  263. }
  264. else {
  265. td1.appendTo(tr);
  266. td2.appendTo(tr);
  267. }
  268. pad = true;
  269. }
  270. idx++;
  271. }
  272. }
  273. tr = null;
  274. td1 = null;
  275. td2 = null;
  276. }
  277. }
  278. return this._elem;
  279. };
  280. $.jqplot.MekkoLegendRenderer.prototype.pack = function(offsets) {
  281. if (this.show) {
  282. // fake a grid for positioning
  283. var grid = {_top:offsets.top, _left:offsets.left, _right:offsets.right, _bottom:this._plotDimensions.height - offsets.bottom};
  284. if (this.placement == 'insideGrid') {
  285. switch (this.location) {
  286. case 'nw':
  287. var a = grid._left + this.xoffset;
  288. var b = grid._top + this.yoffset;
  289. this._elem.css('left', a);
  290. this._elem.css('top', b);
  291. break;
  292. case 'n':
  293. var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
  294. var b = grid._top + this.yoffset;
  295. this._elem.css('left', a);
  296. this._elem.css('top', b);
  297. break;
  298. case 'ne':
  299. var a = offsets.right + this.xoffset;
  300. var b = grid._top + this.yoffset;
  301. this._elem.css({right:a, top:b});
  302. break;
  303. case 'e':
  304. var a = offsets.right + this.xoffset;
  305. var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
  306. this._elem.css({right:a, top:b});
  307. break;
  308. case 'se':
  309. var a = offsets.right + this.xoffset;
  310. var b = offsets.bottom + this.yoffset;
  311. this._elem.css({right:a, bottom:b});
  312. break;
  313. case 's':
  314. var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
  315. var b = offsets.bottom + this.yoffset;
  316. this._elem.css({left:a, bottom:b});
  317. break;
  318. case 'sw':
  319. var a = grid._left + this.xoffset;
  320. var b = offsets.bottom + this.yoffset;
  321. this._elem.css({left:a, bottom:b});
  322. break;
  323. case 'w':
  324. var a = grid._left + this.xoffset;
  325. var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
  326. this._elem.css({left:a, top:b});
  327. break;
  328. default: // same as 'se'
  329. var a = grid._right - this.xoffset;
  330. var b = grid._bottom + this.yoffset;
  331. this._elem.css({right:a, bottom:b});
  332. break;
  333. }
  334. }
  335. else {
  336. switch (this.location) {
  337. case 'nw':
  338. var a = this._plotDimensions.width - grid._left + this.xoffset;
  339. var b = grid._top + this.yoffset;
  340. this._elem.css('right', a);
  341. this._elem.css('top', b);
  342. break;
  343. case 'n':
  344. var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
  345. var b = this._plotDimensions.height - grid._top + this.yoffset;
  346. this._elem.css('left', a);
  347. this._elem.css('bottom', b);
  348. break;
  349. case 'ne':
  350. var a = this._plotDimensions.width - offsets.right + this.xoffset;
  351. var b = grid._top + this.yoffset;
  352. this._elem.css({left:a, top:b});
  353. break;
  354. case 'e':
  355. var a = this._plotDimensions.width - offsets.right + this.xoffset;
  356. var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
  357. this._elem.css({left:a, top:b});
  358. break;
  359. case 'se':
  360. var a = this._plotDimensions.width - offsets.right + this.xoffset;
  361. var b = offsets.bottom + this.yoffset;
  362. this._elem.css({left:a, bottom:b});
  363. break;
  364. case 's':
  365. var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
  366. var b = this._plotDimensions.height - offsets.bottom + this.yoffset;
  367. this._elem.css({left:a, top:b});
  368. break;
  369. case 'sw':
  370. var a = this._plotDimensions.width - grid._left + this.xoffset;
  371. var b = offsets.bottom + this.yoffset;
  372. this._elem.css({right:a, bottom:b});
  373. break;
  374. case 'w':
  375. var a = this._plotDimensions.width - grid._left + this.xoffset;
  376. var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
  377. this._elem.css({right:a, top:b});
  378. break;
  379. default: // same as 'se'
  380. var a = grid._right - this.xoffset;
  381. var b = grid._bottom + this.yoffset;
  382. this._elem.css({right:a, bottom:b});
  383. break;
  384. }
  385. }
  386. }
  387. };
  388. // setup default renderers for axes and legend so user doesn't have to
  389. // called with scope of plot
  390. function preInit(target, data, options) {
  391. options = options || {};
  392. options.axesDefaults = options.axesDefaults || {};
  393. options.legend = options.legend || {};
  394. options.seriesDefaults = options.seriesDefaults || {};
  395. var setopts = false;
  396. if (options.seriesDefaults.renderer == $.jqplot.MekkoRenderer) {
  397. setopts = true;
  398. }
  399. else if (options.series) {
  400. for (var i=0; i < options.series.length; i++) {
  401. if (options.series[i].renderer == $.jqplot.MekkoRenderer) {
  402. setopts = true;
  403. }
  404. }
  405. }
  406. if (setopts) {
  407. options.axesDefaults.renderer = $.jqplot.MekkoAxisRenderer;
  408. options.legend.renderer = $.jqplot.MekkoLegendRenderer;
  409. options.legend.preDraw = true;
  410. }
  411. }
  412. $.jqplot.preInitHooks.push(preInit);
  413. })(jQuery);