jqplot.logAxisRenderer.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534
  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.LogAxisRenderer
  34. * A plugin for a jqPlot to render a logarithmic axis.
  35. *
  36. * To use this renderer, include the plugin in your source
  37. * > <script type="text/javascript" language="javascript" src="plugins/jqplot.logAxisRenderer.js"></script>
  38. *
  39. * and supply the appropriate options to your plot
  40. *
  41. * > {axes:{xaxis:{renderer:$.jqplot.LogAxisRenderer}}}
  42. **/
  43. $.jqplot.LogAxisRenderer = function() {
  44. $.jqplot.LinearAxisRenderer.call(this);
  45. // prop: axisDefaults
  46. // Default properties which will be applied directly to the series.
  47. //
  48. // Group: Properties
  49. //
  50. // Properties
  51. //
  52. // base - the logarithmic base, commonly 2, 10 or Math.E
  53. // tickDistribution - Deprecated. "power" distribution of ticks
  54. // always used. Option has no effect.
  55. this.axisDefaults = {
  56. base : 10,
  57. tickDistribution :'power'
  58. };
  59. };
  60. $.jqplot.LogAxisRenderer.prototype = new $.jqplot.LinearAxisRenderer();
  61. $.jqplot.LogAxisRenderer.prototype.constructor = $.jqplot.LogAxisRenderer;
  62. $.jqplot.LogAxisRenderer.prototype.init = function(options) {
  63. // prop: drawBaseline
  64. // True to draw the axis baseline.
  65. this.drawBaseline = true;
  66. // prop: minorTicks
  67. // Number of ticks to add between "major" ticks.
  68. // Major ticks are ticks supplied by user or auto computed.
  69. // Minor ticks cannot be created by user.
  70. this.minorTicks = 'auto';
  71. this._scalefact = 1.0;
  72. $.extend(true, this, options);
  73. this._autoFormatString = '%d';
  74. this._overrideFormatString = false;
  75. for (var d in this.renderer.axisDefaults) {
  76. if (this[d] == null) {
  77. this[d] = this.renderer.axisDefaults[d];
  78. }
  79. }
  80. this.resetDataBounds();
  81. };
  82. $.jqplot.LogAxisRenderer.prototype.createTicks = function(plot) {
  83. // we're are operating on an axis here
  84. var ticks = this._ticks;
  85. var userTicks = this.ticks;
  86. var name = this.name;
  87. var db = this._dataBounds;
  88. var dim = (this.name.charAt(0) === 'x') ? this._plotDimensions.width : this._plotDimensions.height;
  89. var interval;
  90. var min, max;
  91. var pos1, pos2;
  92. var tt, i;
  93. var threshold = 30;
  94. // For some reason scalefactor is screwing up ticks.
  95. this._scalefact = (Math.max(dim, threshold+1) - threshold)/300;
  96. // if we already have ticks, use them.
  97. // ticks must be in order of increasing value.
  98. if (userTicks.length) {
  99. // ticks could be 1D or 2D array of [val, val, ,,,] or [[val, label], [val, label], ...] or mixed
  100. for (i=0; i<userTicks.length; i++){
  101. var ut = userTicks[i];
  102. var t = new this.tickRenderer(this.tickOptions);
  103. if (ut.constructor == Array) {
  104. t.value = ut[0];
  105. t.label = ut[1];
  106. if (!this.showTicks) {
  107. t.showLabel = false;
  108. t.showMark = false;
  109. }
  110. else if (!this.showTickMarks) {
  111. t.showMark = false;
  112. }
  113. t.setTick(ut[0], this.name);
  114. this._ticks.push(t);
  115. }
  116. else if ($.isPlainObject(ut)) {
  117. $.extend(true, t, ut);
  118. t.axis = this.name;
  119. this._ticks.push(t);
  120. }
  121. else {
  122. t.value = ut;
  123. if (!this.showTicks) {
  124. t.showLabel = false;
  125. t.showMark = false;
  126. }
  127. else if (!this.showTickMarks) {
  128. t.showMark = false;
  129. }
  130. t.setTick(ut, this.name);
  131. this._ticks.push(t);
  132. }
  133. }
  134. this.numberTicks = userTicks.length;
  135. this.min = this._ticks[0].value;
  136. this.max = this._ticks[this.numberTicks-1].value;
  137. }
  138. // we don't have any ticks yet, let's make some!
  139. else if (this.min == null && this.max == null) {
  140. min = db.min * (2 - this.padMin);
  141. max = db.max * this.padMax;
  142. // if min and max are same, space them out a bit
  143. if (min == max) {
  144. var adj = 0.05;
  145. min = min*(1-adj);
  146. max = max*(1+adj);
  147. }
  148. // perform some checks
  149. if (this.min != null && this.min <= 0) {
  150. throw new Error("Log axis minimum must be greater than 0");
  151. }
  152. if (this.max != null && this.max <= 0) {
  153. throw new Error("Log axis maximum must be greater than 0");
  154. }
  155. function findCeil (val) {
  156. var order = Math.pow(10, Math.floor(Math.log(val)/Math.LN10));
  157. return Math.ceil(val/order) * order;
  158. }
  159. function findFloor(val) {
  160. var order = Math.pow(10, Math.floor(Math.log(val)/Math.LN10));
  161. return Math.floor(val/order) * order;
  162. }
  163. // var range = max - min;
  164. var rmin, rmax;
  165. // for power distribution, open up range to get a nice power of axis.renderer.base.
  166. // power distribution won't respect the user's min/max settings.
  167. rmin = Math.pow(this.base, Math.floor(Math.log(min)/Math.log(this.base)));
  168. rmax = Math.pow(this.base, Math.ceil(Math.log(max)/Math.log(this.base)));
  169. // // if min and max are same, space them out a bit
  170. // if (rmin === rmax) {
  171. // var adj = 0.05;
  172. // rmin = rmin*(1-adj);
  173. // rmax = rmax*(1+adj);
  174. // }
  175. // Handle case where a data value was zero
  176. if (rmin === 0) {
  177. rmin = 1;
  178. }
  179. var order = Math.round(Math.log(rmin)/Math.LN10);
  180. if (this.tickOptions == null || !this.tickOptions.formatString) {
  181. this._overrideFormatString = true;
  182. }
  183. this.min = rmin;
  184. this.max = rmax;
  185. var range = this.max - this.min;
  186. var minorTicks = (this.minorTicks === 'auto') ? 0 : this.minorTicks;
  187. var numberTicks;
  188. if (this.numberTicks == null){
  189. if (dim > 140) {
  190. numberTicks = Math.round(Math.log(this.max/this.min)/Math.log(this.base) + 1);
  191. if (numberTicks < 2) {
  192. numberTicks = 2;
  193. }
  194. if (minorTicks === 0) {
  195. var temp = dim/(numberTicks - 1);
  196. if (temp < 100) {
  197. minorTicks = 0;
  198. }
  199. else if (temp < 190) {
  200. minorTicks = 1;
  201. }
  202. else if (temp < 250) {
  203. minorTicks = 3;
  204. }
  205. else if (temp < 600) {
  206. minorTicks = 4;
  207. }
  208. else {
  209. minorTicks = 9;
  210. }
  211. }
  212. }
  213. else {
  214. numberTicks = 2;
  215. if (minorTicks === 0) {
  216. minorTicks = 1;
  217. }
  218. minorTicks = 0;
  219. }
  220. }
  221. else {
  222. numberTicks = this.numberTicks;
  223. }
  224. if (order >= 0 && minorTicks !== 3) {
  225. this._autoFormatString = '%d';
  226. }
  227. // Adjust format string for case with 3 ticks where we'll have like 1, 2.5, 5, 7.5, 10
  228. else if (order <= 0 && minorTicks === 3) {
  229. var temp = -(order - 1);
  230. this._autoFormatString = '%.'+ Math.abs(order-1) + 'f';
  231. }
  232. // Adjust format string for values less than 1.
  233. else if (order < 0) {
  234. var temp = -order;
  235. this._autoFormatString = '%.'+ Math.abs(order) + 'f';
  236. }
  237. else {
  238. this._autoFormatString = '%d';
  239. }
  240. var to, t, val, tt1, spread, interval;
  241. for (var i=0; i<numberTicks; i++){
  242. tt = Math.pow(this.base, i - numberTicks + 1) * this.max;
  243. t = new this.tickRenderer(this.tickOptions);
  244. if (this._overrideFormatString) {
  245. t.formatString = this._autoFormatString;
  246. }
  247. if (!this.showTicks) {
  248. t.showLabel = false;
  249. t.showMark = false;
  250. }
  251. else if (!this.showTickMarks) {
  252. t.showMark = false;
  253. }
  254. t.setTick(tt, this.name);
  255. this._ticks.push(t);
  256. if (minorTicks && i<numberTicks-1) {
  257. tt1 = Math.pow(this.base, i - numberTicks + 2) * this.max;
  258. spread = tt1 - tt;
  259. interval = tt1 / (minorTicks+1);
  260. for (var j=minorTicks-1; j>=0; j--) {
  261. val = tt1-interval*(j+1);
  262. t = new this.tickRenderer(this.tickOptions);
  263. if (this._overrideFormatString && this._autoFormatString != '') {
  264. t.formatString = this._autoFormatString;
  265. }
  266. if (!this.showTicks) {
  267. t.showLabel = false;
  268. t.showMark = false;
  269. }
  270. else if (!this.showTickMarks) {
  271. t.showMark = false;
  272. }
  273. t.setTick(val, this.name);
  274. this._ticks.push(t);
  275. }
  276. }
  277. }
  278. }
  279. // min and max are set as would be the case with zooming
  280. else if (this.min != null && this.max != null) {
  281. var opts = $.extend(true, {}, this.tickOptions, {name: this.name, value: null});
  282. var nt, ti;
  283. // don't have an interval yet, pick one that gives the most
  284. // "round" ticks we can get.
  285. if (this.numberTicks == null && this.tickInterval == null) {
  286. // var threshold = 30;
  287. var tdim = Math.max(dim, threshold+1);
  288. var nttarget = Math.ceil((tdim-threshold)/35 + 1);
  289. var ret = $.jqplot.LinearTickGenerator.bestConstrainedInterval(this.min, this.max, nttarget);
  290. this._autoFormatString = ret[3];
  291. nt = ret[2];
  292. ti = ret[4];
  293. for (var i=0; i<nt; i++) {
  294. opts.value = this.min + i * ti;
  295. t = new this.tickRenderer(opts);
  296. if (this._overrideFormatString && this._autoFormatString != '') {
  297. t.formatString = this._autoFormatString;
  298. }
  299. if (!this.showTicks) {
  300. t.showLabel = false;
  301. t.showMark = false;
  302. }
  303. else if (!this.showTickMarks) {
  304. t.showMark = false;
  305. }
  306. this._ticks.push(t);
  307. }
  308. }
  309. // for loose zoom, number ticks and interval are also set.
  310. else if (this.numberTicks != null && this.tickInterval != null) {
  311. nt = this.numberTicks;
  312. for (var i=0; i<nt; i++) {
  313. opts.value = this.min + i * this.tickInterval;
  314. t = new this.tickRenderer(opts);
  315. if (this._overrideFormatString && this._autoFormatString != '') {
  316. t.formatString = this._autoFormatString;
  317. }
  318. if (!this.showTicks) {
  319. t.showLabel = false;
  320. t.showMark = false;
  321. }
  322. else if (!this.showTickMarks) {
  323. t.showMark = false;
  324. }
  325. this._ticks.push(t);
  326. }
  327. }
  328. }
  329. };
  330. $.jqplot.LogAxisRenderer.prototype.pack = function(pos, offsets) {
  331. var lb = parseInt(this.base, 10);
  332. var ticks = this._ticks;
  333. var trans = function (v) { return Math.log(v)/Math.log(lb); };
  334. var invtrans = function (v) { return Math.pow(Math.E, (Math.log(lb)*v)); };
  335. var max = trans(this.max);
  336. var min = trans(this.min);
  337. var offmax = offsets.max;
  338. var offmin = offsets.min;
  339. var lshow = (this._label == null) ? false : this._label.show;
  340. for (var p in pos) {
  341. this._elem.css(p, pos[p]);
  342. }
  343. this._offsets = offsets;
  344. // pixellength will be + for x axes and - for y axes becasue pixels always measured from top left.
  345. var pixellength = offmax - offmin;
  346. var unitlength = max - min;
  347. // point to unit and unit to point conversions references to Plot DOM element top left corner.
  348. this.p2u = function(p){
  349. return invtrans((p - offmin) * unitlength / pixellength + min);
  350. };
  351. this.u2p = function(u){
  352. return (trans(u) - min) * pixellength / unitlength + offmin;
  353. };
  354. if (this.name == 'xaxis' || this.name == 'x2axis'){
  355. this.series_u2p = function(u){
  356. return (trans(u) - min) * pixellength / unitlength;
  357. };
  358. this.series_p2u = function(p){
  359. return invtrans(p * unitlength / pixellength + min);
  360. };
  361. }
  362. // yaxis is max at top of canvas.
  363. else {
  364. this.series_u2p = function(u){
  365. return (trans(u) - max) * pixellength / unitlength;
  366. };
  367. this.series_p2u = function(p){
  368. return invtrans(p * unitlength / pixellength + max);
  369. };
  370. }
  371. if (this.show) {
  372. if (this.name == 'xaxis' || this.name == 'x2axis') {
  373. for (var i=0; i<ticks.length; i++) {
  374. var t = ticks[i];
  375. if (t.show && t.showLabel) {
  376. var shim;
  377. if (t.constructor == $.jqplot.CanvasAxisTickRenderer && t.angle) {
  378. switch (t.labelPosition) {
  379. case 'auto':
  380. // position at end
  381. if (t.angle < 0) {
  382. shim = -t.getWidth() + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
  383. }
  384. // position at start
  385. else {
  386. shim = -t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
  387. }
  388. break;
  389. case 'end':
  390. shim = -t.getWidth() + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
  391. break;
  392. case 'start':
  393. shim = -t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
  394. break;
  395. case 'middle':
  396. shim = -t.getWidth()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
  397. break;
  398. default:
  399. shim = -t.getWidth()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
  400. break;
  401. }
  402. }
  403. else {
  404. shim = -t.getWidth()/2;
  405. }
  406. // var shim = t.getWidth()/2;
  407. var val = this.u2p(t.value) + shim + 'px';
  408. t._elem.css('left', val);
  409. t.pack();
  410. }
  411. }
  412. if (lshow) {
  413. var w = this._label._elem.outerWidth(true);
  414. this._label._elem.css('left', offmin + pixellength/2 - w/2 + 'px');
  415. if (this.name == 'xaxis') {
  416. this._label._elem.css('bottom', '0px');
  417. }
  418. else {
  419. this._label._elem.css('top', '0px');
  420. }
  421. this._label.pack();
  422. }
  423. }
  424. else {
  425. for (var i=0; i<ticks.length; i++) {
  426. var t = ticks[i];
  427. if (t.show && t.showLabel) {
  428. var shim;
  429. if (t.constructor == $.jqplot.CanvasAxisTickRenderer && t.angle) {
  430. switch (t.labelPosition) {
  431. case 'auto':
  432. // position at end
  433. case 'end':
  434. if (t.angle < 0) {
  435. shim = -t._textRenderer.height * Math.cos(-t._textRenderer.angle) / 2;
  436. }
  437. else {
  438. shim = -t.getHeight() + t._textRenderer.height * Math.cos(t._textRenderer.angle) / 2;
  439. }
  440. break;
  441. case 'start':
  442. if (t.angle > 0) {
  443. shim = -t._textRenderer.height * Math.cos(-t._textRenderer.angle) / 2;
  444. }
  445. else {
  446. shim = -t.getHeight() + t._textRenderer.height * Math.cos(t._textRenderer.angle) / 2;
  447. }
  448. break;
  449. case 'middle':
  450. // if (t.angle > 0) {
  451. // shim = -t.getHeight()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
  452. // }
  453. // else {
  454. // shim = -t.getHeight()/2 - t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
  455. // }
  456. shim = -t.getHeight()/2;
  457. break;
  458. default:
  459. shim = -t.getHeight()/2;
  460. break;
  461. }
  462. }
  463. else {
  464. shim = -t.getHeight()/2;
  465. }
  466. var val = this.u2p(t.value) + shim + 'px';
  467. t._elem.css('top', val);
  468. t.pack();
  469. }
  470. }
  471. if (lshow) {
  472. var h = this._label._elem.outerHeight(true);
  473. this._label._elem.css('top', offmax - pixellength/2 - h/2 + 'px');
  474. if (this.name == 'yaxis') {
  475. this._label._elem.css('left', '0px');
  476. }
  477. else {
  478. this._label._elem.css('right', '0px');
  479. }
  480. this._label.pack();
  481. }
  482. }
  483. }
  484. };
  485. })(jQuery);