jqplot.mekkoAxisRenderer.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611
  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. // class: $.jqplot.MekkoAxisRenderer
  33. // An axis renderer for a Mekko chart.
  34. // Should be used with a Mekko chart where the mekkoRenderer is used on the series.
  35. // Displays the Y axis as a range from 0 to 1 (0 to 100%) and the x axis with a tick
  36. // for each series scaled to the sum of all the y values.
  37. $.jqplot.MekkoAxisRenderer = function() {
  38. };
  39. // called with scope of axis object.
  40. $.jqplot.MekkoAxisRenderer.prototype.init = function(options){
  41. // prop: tickMode
  42. // How to space the ticks on the axis.
  43. // 'bar' will place a tick at the width of each bar.
  44. // This is the default for the x axis.
  45. // 'even' will place ticks at even intervals. This is
  46. // the default for x2 axis and y axis. y axis cannot be changed.
  47. this.tickMode;
  48. // prop: barLabelRenderer
  49. // renderer to use to draw labels under each bar.
  50. this.barLabelRenderer = $.jqplot.AxisLabelRenderer;
  51. // prop: barLabels
  52. // array of labels to put under each bar.
  53. this.barLabels = this.barLabels || [];
  54. // prop: barLabelOptions
  55. // options object to pass to the bar label renderer.
  56. this.barLabelOptions = {};
  57. this.tickOptions = $.extend(true, {showGridline:false}, this.tickOptions);
  58. this._barLabels = [];
  59. $.extend(true, this, options);
  60. if (this.name == 'yaxis') {
  61. this.tickOptions.formatString = this.tickOptions.formatString || "%d\%";
  62. }
  63. var db = this._dataBounds;
  64. db.min = 0;
  65. // for y axes, scale always go from 0 to 1 (0 to 100%)
  66. if (this.name == 'yaxis' || this.name == 'y2axis') {
  67. db.max = 100;
  68. this.tickMode = 'even';
  69. }
  70. // For x axes, scale goes from 0 to sum of all y values.
  71. else if (this.name == 'xaxis'){
  72. this.tickMode = (this.tickMode == null) ? 'bar' : this.tickMode;
  73. for (var i=0; i<this._series.length; i++) {
  74. db.max += this._series[i]._sumy;
  75. }
  76. }
  77. else if (this.name == 'x2axis'){
  78. this.tickMode = (this.tickMode == null) ? 'even' : this.tickMode;
  79. for (var i=0; i<this._series.length; i++) {
  80. db.max += this._series[i]._sumy;
  81. }
  82. }
  83. };
  84. // called with scope of axis
  85. $.jqplot.MekkoAxisRenderer.prototype.draw = function(ctx, plot) {
  86. if (this.show) {
  87. // populate the axis label and value properties.
  88. // createTicks is a method on the renderer, but
  89. // call it within the scope of the axis.
  90. this.renderer.createTicks.call(this);
  91. // fill a div with axes labels in the right direction.
  92. // Need to pregenerate each axis to get its bounds and
  93. // position it and the labels correctly on the plot.
  94. var dim=0;
  95. var temp;
  96. var elem = document.createElement('div');
  97. this._elem = $(elem);
  98. this._elem.addClass('jqplot-axis jqplot-'+this.name);
  99. this._elem.css('position', 'absolute');
  100. elem = null;
  101. if (this.name == 'xaxis' || this.name == 'x2axis') {
  102. this._elem.width(this._plotDimensions.width);
  103. }
  104. else {
  105. this._elem.height(this._plotDimensions.height);
  106. }
  107. // draw the axis label
  108. // create a _label object.
  109. this.labelOptions.axis = this.name;
  110. this._label = new this.labelRenderer(this.labelOptions);
  111. if (this._label.show) {
  112. this._elem.append(this._label.draw(ctx));
  113. }
  114. var t, tick, elem;
  115. if (this.showTicks) {
  116. t = this._ticks;
  117. for (var i=0; i<t.length; i++) {
  118. tick = t[i];
  119. if (tick.showLabel && (!tick.isMinorTick || this.showMinorTicks)) {
  120. this._elem.append(tick.draw(ctx));
  121. }
  122. }
  123. }
  124. // draw the series labels
  125. for (i=0; i<this.barLabels.length; i++) {
  126. this.barLabelOptions.axis = this.name;
  127. this.barLabelOptions.label = this.barLabels[i];
  128. this._barLabels.push(new this.barLabelRenderer(this.barLabelOptions));
  129. if (this.tickMode != 'bar') {
  130. this._barLabels[i].show = false;
  131. }
  132. if (this._barLabels[i].show) {
  133. var elem = this._barLabels[i].draw(ctx, plot);
  134. elem.removeClass('jqplot-'+this.name+'-label');
  135. elem.addClass('jqplot-'+this.name+'-tick');
  136. elem.addClass('jqplot-mekko-barLabel');
  137. elem.appendTo(this._elem);
  138. elem = null;
  139. }
  140. }
  141. }
  142. return this._elem;
  143. };
  144. // called with scope of an axis
  145. $.jqplot.MekkoAxisRenderer.prototype.reset = function() {
  146. this.min = this._min;
  147. this.max = this._max;
  148. this.tickInterval = this._tickInterval;
  149. this.numberTicks = this._numberTicks;
  150. // this._ticks = this.__ticks;
  151. };
  152. // called with scope of axis
  153. $.jqplot.MekkoAxisRenderer.prototype.set = function() {
  154. var dim = 0;
  155. var temp;
  156. var w = 0;
  157. var h = 0;
  158. var lshow = (this._label == null) ? false : this._label.show;
  159. if (this.show && this.showTicks) {
  160. var t = this._ticks;
  161. for (var i=0; i<t.length; i++) {
  162. var tick = t[i];
  163. if (tick.showLabel && (!tick.isMinorTick || this.showMinorTicks)) {
  164. if (this.name == 'xaxis' || this.name == 'x2axis') {
  165. temp = tick._elem.outerHeight(true);
  166. }
  167. else {
  168. temp = tick._elem.outerWidth(true);
  169. }
  170. if (temp > dim) {
  171. dim = temp;
  172. }
  173. }
  174. }
  175. if (lshow) {
  176. w = this._label._elem.outerWidth(true);
  177. h = this._label._elem.outerHeight(true);
  178. }
  179. if (this.name == 'xaxis') {
  180. dim = dim + h;
  181. this._elem.css({'height':dim+'px', left:'0px', bottom:'0px'});
  182. }
  183. else if (this.name == 'x2axis') {
  184. dim = dim + h;
  185. this._elem.css({'height':dim+'px', left:'0px', top:'0px'});
  186. }
  187. else if (this.name == 'yaxis') {
  188. dim = dim + w;
  189. this._elem.css({'width':dim+'px', left:'0px', top:'0px'});
  190. if (lshow && this._label.constructor == $.jqplot.AxisLabelRenderer) {
  191. this._label._elem.css('width', w+'px');
  192. }
  193. }
  194. else {
  195. dim = dim + w;
  196. this._elem.css({'width':dim+'px', right:'0px', top:'0px'});
  197. if (lshow && this._label.constructor == $.jqplot.AxisLabelRenderer) {
  198. this._label._elem.css('width', w+'px');
  199. }
  200. }
  201. }
  202. };
  203. // called with scope of axis
  204. $.jqplot.MekkoAxisRenderer.prototype.createTicks = function() {
  205. // we're are operating on an axis here
  206. var ticks = this._ticks;
  207. var userTicks = this.ticks;
  208. var name = this.name;
  209. // databounds were set on axis initialization.
  210. var db = this._dataBounds;
  211. var dim, interval;
  212. var min, max;
  213. var pos1, pos2;
  214. var t, tt, i, j;
  215. // if we already have ticks, use them.
  216. // ticks must be in order of increasing value.
  217. if (userTicks.length) {
  218. // ticks could be 1D or 2D array of [val, val, ,,,] or [[val, label], [val, label], ...] or mixed
  219. for (i=0; i<userTicks.length; i++){
  220. var ut = userTicks[i];
  221. var t = new this.tickRenderer(this.tickOptions);
  222. if (ut.constructor == Array) {
  223. t.value = ut[0];
  224. t.label = ut[1];
  225. if (!this.showTicks) {
  226. t.showLabel = false;
  227. t.showMark = false;
  228. }
  229. else if (!this.showTickMarks) {
  230. t.showMark = false;
  231. }
  232. t.setTick(ut[0], this.name);
  233. this._ticks.push(t);
  234. }
  235. else {
  236. t.value = ut;
  237. if (!this.showTicks) {
  238. t.showLabel = false;
  239. t.showMark = false;
  240. }
  241. else if (!this.showTickMarks) {
  242. t.showMark = false;
  243. }
  244. t.setTick(ut, this.name);
  245. this._ticks.push(t);
  246. }
  247. }
  248. this.numberTicks = userTicks.length;
  249. this.min = this._ticks[0].value;
  250. this.max = this._ticks[this.numberTicks-1].value;
  251. this.tickInterval = (this.max - this.min) / (this.numberTicks - 1);
  252. }
  253. // we don't have any ticks yet, let's make some!
  254. else {
  255. if (name == 'xaxis' || name == 'x2axis') {
  256. dim = this._plotDimensions.width;
  257. }
  258. else {
  259. dim = this._plotDimensions.height;
  260. }
  261. // if min, max and number of ticks specified, user can't specify interval.
  262. if (this.min != null && this.max != null && this.numberTicks != null) {
  263. this.tickInterval = null;
  264. }
  265. min = (this.min != null) ? this.min : db.min;
  266. max = (this.max != null) ? this.max : db.max;
  267. // if min and max are same, space them out a bit.+
  268. if (min == max) {
  269. var adj = 0.05;
  270. if (min > 0) {
  271. adj = Math.max(Math.log(min)/Math.LN10, 0.05);
  272. }
  273. min -= adj;
  274. max += adj;
  275. }
  276. var range = max - min;
  277. var rmin, rmax;
  278. var temp, prev, curr;
  279. var ynumticks = [3,5,6,11,21];
  280. // yaxis divide ticks in nice intervals from 0 to 1.
  281. if (this.name == 'yaxis' || this.name == 'y2axis') {
  282. this.min = 0;
  283. this.max = 100;
  284. // user didn't specify number of ticks.
  285. if (!this.numberTicks){
  286. if (this.tickInterval) {
  287. this.numberTicks = 3 + Math.ceil(range / this.tickInterval);
  288. }
  289. else {
  290. temp = 2 + Math.ceil((dim-(this.tickSpacing-1))/this.tickSpacing);
  291. for (i=0; i<ynumticks.length; i++) {
  292. curr = temp/ynumticks[i];
  293. if (curr == 1) {
  294. this.numberTicks = ynumticks[i];
  295. break;
  296. }
  297. else if (curr > 1) {
  298. prev = curr;
  299. continue;
  300. }
  301. else if (curr < 1) {
  302. // was prev or is curr closer to one?
  303. if (Math.abs(prev - 1) < Math.abs(curr - 1)) {
  304. this.numberTicks = ynumticks[i-1];
  305. break;
  306. }
  307. else {
  308. this.numberTicks = ynumticks[i];
  309. break;
  310. }
  311. }
  312. else if (i == ynumticks.length -1) {
  313. this.numberTicks = ynumticks[i];
  314. }
  315. }
  316. this.tickInterval = range / (this.numberTicks - 1);
  317. }
  318. }
  319. // user did specify number of ticks.
  320. else {
  321. this.tickInterval = range / (this.numberTicks - 1);
  322. }
  323. for (var i=0; i<this.numberTicks; i++){
  324. tt = this.min + i * this.tickInterval;
  325. t = new this.tickRenderer(this.tickOptions);
  326. // var t = new $.jqplot.AxisTickRenderer(this.tickOptions);
  327. if (!this.showTicks) {
  328. t.showLabel = false;
  329. t.showMark = false;
  330. }
  331. else if (!this.showTickMarks) {
  332. t.showMark = false;
  333. }
  334. t.setTick(tt, this.name);
  335. this._ticks.push(t);
  336. }
  337. }
  338. // for x axes, have number ot ticks equal to number of series and ticks placed
  339. // at sum of y values for each series.
  340. else if (this.tickMode == 'bar') {
  341. this.min = 0;
  342. this.numberTicks = this._series.length + 1;
  343. t = new this.tickRenderer(this.tickOptions);
  344. if (!this.showTicks) {
  345. t.showLabel = false;
  346. t.showMark = false;
  347. }
  348. else if (!this.showTickMarks) {
  349. t.showMark = false;
  350. }
  351. t.setTick(0, this.name);
  352. this._ticks.push(t);
  353. temp = 0;
  354. for (i=1; i<this.numberTicks; i++){
  355. temp += this._series[i-1]._sumy;
  356. t = new this.tickRenderer(this.tickOptions);
  357. if (!this.showTicks) {
  358. t.showLabel = false;
  359. t.showMark = false;
  360. }
  361. else if (!this.showTickMarks) {
  362. t.showMark = false;
  363. }
  364. t.setTick(temp, this.name);
  365. this._ticks.push(t);
  366. }
  367. this.max = this.max || temp;
  368. // if user specified a max and it is greater than sum, add a tick
  369. if (this.max > temp) {
  370. t = new this.tickRenderer(this.tickOptions);
  371. if (!this.showTicks) {
  372. t.showLabel = false;
  373. t.showMark = false;
  374. }
  375. else if (!this.showTickMarks) {
  376. t.showMark = false;
  377. }
  378. t.setTick(this.max, this.name);
  379. this._ticks.push(t);
  380. }
  381. }
  382. else if (this.tickMode == 'even') {
  383. this.min = 0;
  384. this.max = this.max || db.max;
  385. // get a desired number of ticks
  386. var nt = 2 + Math.ceil((dim-(this.tickSpacing-1))/this.tickSpacing);
  387. range = this.max - this.min;
  388. this.numberTicks = nt;
  389. this.tickInterval = range / (this.numberTicks - 1);
  390. for (i=0; i<this.numberTicks; i++){
  391. tt = this.min + i * this.tickInterval;
  392. t = new this.tickRenderer(this.tickOptions);
  393. // var t = new $.jqplot.AxisTickRenderer(this.tickOptions);
  394. if (!this.showTicks) {
  395. t.showLabel = false;
  396. t.showMark = false;
  397. }
  398. else if (!this.showTickMarks) {
  399. t.showMark = false;
  400. }
  401. t.setTick(tt, this.name);
  402. this._ticks.push(t);
  403. }
  404. }
  405. }
  406. };
  407. // called with scope of axis
  408. $.jqplot.MekkoAxisRenderer.prototype.pack = function(pos, offsets) {
  409. var ticks = this._ticks;
  410. var max = this.max;
  411. var min = this.min;
  412. var offmax = offsets.max;
  413. var offmin = offsets.min;
  414. var lshow = (this._label == null) ? false : this._label.show;
  415. for (var p in pos) {
  416. this._elem.css(p, pos[p]);
  417. }
  418. this._offsets = offsets;
  419. // pixellength will be + for x axes and - for y axes becasue pixels always measured from top left.
  420. var pixellength = offmax - offmin;
  421. var unitlength = max - min;
  422. // point to unit and unit to point conversions references to Plot DOM element top left corner.
  423. this.p2u = function(p){
  424. return (p - offmin) * unitlength / pixellength + min;
  425. };
  426. this.u2p = function(u){
  427. return (u - min) * pixellength / unitlength + offmin;
  428. };
  429. if (this.name == 'xaxis' || this.name == 'x2axis'){
  430. this.series_u2p = function(u){
  431. return (u - min) * pixellength / unitlength;
  432. };
  433. this.series_p2u = function(p){
  434. return p * unitlength / pixellength + min;
  435. };
  436. }
  437. else {
  438. this.series_u2p = function(u){
  439. return (u - max) * pixellength / unitlength;
  440. };
  441. this.series_p2u = function(p){
  442. return p * unitlength / pixellength + max;
  443. };
  444. }
  445. if (this.show) {
  446. if (this.name == 'xaxis' || this.name == 'x2axis') {
  447. for (var i=0; i<ticks.length; i++) {
  448. var t = ticks[i];
  449. if (t.show && t.showLabel) {
  450. var shim;
  451. if (t.constructor == $.jqplot.CanvasAxisTickRenderer && t.angle) {
  452. // will need to adjust auto positioning based on which axis this is.
  453. var temp = (this.name == 'xaxis') ? 1 : -1;
  454. switch (t.labelPosition) {
  455. case 'auto':
  456. // position at end
  457. if (temp * t.angle < 0) {
  458. shim = -t.getWidth() + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
  459. }
  460. // position at start
  461. else {
  462. shim = -t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
  463. }
  464. break;
  465. case 'end':
  466. shim = -t.getWidth() + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
  467. break;
  468. case 'start':
  469. shim = -t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
  470. break;
  471. case 'middle':
  472. shim = -t.getWidth()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
  473. break;
  474. default:
  475. shim = -t.getWidth()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
  476. break;
  477. }
  478. }
  479. else {
  480. shim = -t.getWidth()/2;
  481. }
  482. var val = this.u2p(t.value) + shim + 'px';
  483. t._elem.css('left', val);
  484. t.pack();
  485. }
  486. }
  487. var w;
  488. if (lshow) {
  489. w = this._label._elem.outerWidth(true);
  490. this._label._elem.css('left', offmin + pixellength/2 - w/2 + 'px');
  491. if (this.name == 'xaxis') {
  492. this._label._elem.css('bottom', '0px');
  493. }
  494. else {
  495. this._label._elem.css('top', '0px');
  496. }
  497. this._label.pack();
  498. }
  499. // now show the labels under the bars.
  500. var b, l, r;
  501. for (var i=0; i<this.barLabels.length; i++) {
  502. b = this._barLabels[i];
  503. if (b.show) {
  504. w = b.getWidth();
  505. l = this._ticks[i].getLeft() + this._ticks[i].getWidth();
  506. r = this._ticks[i+1].getLeft();
  507. b._elem.css('left', (r+l-w)/2+'px');
  508. b._elem.css('top', this._ticks[i]._elem.css('top'));
  509. b.pack();
  510. }
  511. }
  512. }
  513. else {
  514. for (var i=0; i<ticks.length; i++) {
  515. var t = ticks[i];
  516. if (t.show && t.showLabel) {
  517. var shim;
  518. if (t.constructor == $.jqplot.CanvasAxisTickRenderer && t.angle) {
  519. // will need to adjust auto positioning based on which axis this is.
  520. var temp = (this.name == 'yaxis') ? 1 : -1;
  521. switch (t.labelPosition) {
  522. case 'auto':
  523. // position at end
  524. case 'end':
  525. if (temp * t.angle < 0) {
  526. shim = -t._textRenderer.height * Math.cos(-t._textRenderer.angle) / 2;
  527. }
  528. else {
  529. shim = -t.getHeight() + t._textRenderer.height * Math.cos(t._textRenderer.angle) / 2;
  530. }
  531. break;
  532. case 'start':
  533. if (t.angle > 0) {
  534. shim = -t._textRenderer.height * Math.cos(-t._textRenderer.angle) / 2;
  535. }
  536. else {
  537. shim = -t.getHeight() + t._textRenderer.height * Math.cos(t._textRenderer.angle) / 2;
  538. }
  539. break;
  540. case 'middle':
  541. shim = -t.getHeight()/2;
  542. break;
  543. default:
  544. shim = -t.getHeight()/2;
  545. break;
  546. }
  547. }
  548. else {
  549. shim = -t.getHeight()/2;
  550. }
  551. var val = this.u2p(t.value) + shim + 'px';
  552. t._elem.css('top', val);
  553. t.pack();
  554. }
  555. }
  556. if (lshow) {
  557. var h = this._label._elem.outerHeight(true);
  558. this._label._elem.css('top', offmax - pixellength/2 - h/2 + 'px');
  559. if (this.name == 'yaxis') {
  560. this._label._elem.css('left', '0px');
  561. }
  562. else {
  563. this._label._elem.css('right', '0px');
  564. }
  565. this._label.pack();
  566. }
  567. }
  568. }
  569. };
  570. })(jQuery);