jqplot.logAxisRenderer.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  1. /**
  2. * jqPlot
  3. * Pure JavaScript plotting plugin using jQuery
  4. *
  5. * Version: 1.0.2
  6. * Revision: 1108
  7. *
  8. * Copyright (c) 2009-2011 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('log axis minimum must be greater than 0');
  151. }
  152. if (this.max != null && this.max <= 0) {
  153. throw('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. var order = Math.round(Math.log(rmin)/Math.LN10);
  176. if (this.tickOptions == null || !this.tickOptions.formatString) {
  177. this._overrideFormatString = true;
  178. }
  179. this.min = rmin;
  180. this.max = rmax;
  181. var range = this.max - this.min;
  182. var minorTicks = (this.minorTicks === 'auto') ? 0 : this.minorTicks;
  183. var numberTicks;
  184. if (this.numberTicks == null){
  185. if (dim > 140) {
  186. numberTicks = Math.round(Math.log(this.max/this.min)/Math.log(this.base) + 1);
  187. if (numberTicks < 2) {
  188. numberTicks = 2;
  189. }
  190. if (minorTicks === 0) {
  191. var temp = dim/(numberTicks - 1);
  192. if (temp < 100) {
  193. minorTicks = 0;
  194. }
  195. else if (temp < 190) {
  196. minorTicks = 1;
  197. }
  198. else if (temp < 250) {
  199. minorTicks = 3;
  200. }
  201. else if (temp < 600) {
  202. minorTicks = 4;
  203. }
  204. else {
  205. minorTicks = 9;
  206. }
  207. }
  208. }
  209. else {
  210. numberTicks = 2;
  211. if (minorTicks === 0) {
  212. minorTicks = 1;
  213. }
  214. minorTicks = 0;
  215. }
  216. }
  217. else {
  218. numberTicks = this.numberTicks;
  219. }
  220. if (order >= 0 && minorTicks !== 3) {
  221. this._autoFormatString = '%d';
  222. }
  223. // Adjust format string for case with 3 ticks where we'll have like 1, 2.5, 5, 7.5, 10
  224. else if (order <= 0 && minorTicks === 3) {
  225. var temp = -(order - 1);
  226. this._autoFormatString = '%.'+ Math.abs(order-1) + 'f';
  227. }
  228. // Adjust format string for values less than 1.
  229. else if (order < 0) {
  230. var temp = -order;
  231. this._autoFormatString = '%.'+ Math.abs(order) + 'f';
  232. }
  233. else {
  234. this._autoFormatString = '%d';
  235. }
  236. var to, t, val, tt1, spread, interval;
  237. for (var i=0; i<numberTicks; i++){
  238. tt = Math.pow(this.base, i - numberTicks + 1) * this.max;
  239. t = new this.tickRenderer(this.tickOptions);
  240. if (this._overrideFormatString) {
  241. t.formatString = this._autoFormatString;
  242. }
  243. if (!this.showTicks) {
  244. t.showLabel = false;
  245. t.showMark = false;
  246. }
  247. else if (!this.showTickMarks) {
  248. t.showMark = false;
  249. }
  250. t.setTick(tt, this.name);
  251. this._ticks.push(t);
  252. if (minorTicks && i<numberTicks-1) {
  253. tt1 = Math.pow(this.base, i - numberTicks + 2) * this.max;
  254. spread = tt1 - tt;
  255. interval = tt1 / (minorTicks+1);
  256. for (var j=minorTicks-1; j>=0; j--) {
  257. val = tt1-interval*(j+1);
  258. t = new this.tickRenderer(this.tickOptions);
  259. if (this._overrideFormatString && this._autoFormatString != '') {
  260. t.formatString = this._autoFormatString;
  261. }
  262. if (!this.showTicks) {
  263. t.showLabel = false;
  264. t.showMark = false;
  265. }
  266. else if (!this.showTickMarks) {
  267. t.showMark = false;
  268. }
  269. t.setTick(val, this.name);
  270. this._ticks.push(t);
  271. }
  272. }
  273. }
  274. }
  275. // min and max are set as would be the case with zooming
  276. else if (this.min != null && this.max != null) {
  277. var opts = $.extend(true, {}, this.tickOptions, {name: this.name, value: null});
  278. var nt, ti;
  279. // don't have an interval yet, pick one that gives the most
  280. // "round" ticks we can get.
  281. if (this.numberTicks == null && this.tickInterval == null) {
  282. // var threshold = 30;
  283. var tdim = Math.max(dim, threshold+1);
  284. var nttarget = Math.ceil((tdim-threshold)/35 + 1);
  285. var ret = $.jqplot.LinearTickGenerator.bestConstrainedInterval(this.min, this.max, nttarget);
  286. this._autoFormatString = ret[3];
  287. nt = ret[2];
  288. ti = ret[4];
  289. for (var i=0; i<nt; i++) {
  290. opts.value = this.min + i * ti;
  291. t = new this.tickRenderer(opts);
  292. if (this._overrideFormatString && this._autoFormatString != '') {
  293. t.formatString = this._autoFormatString;
  294. }
  295. if (!this.showTicks) {
  296. t.showLabel = false;
  297. t.showMark = false;
  298. }
  299. else if (!this.showTickMarks) {
  300. t.showMark = false;
  301. }
  302. this._ticks.push(t);
  303. }
  304. }
  305. // for loose zoom, number ticks and interval are also set.
  306. else if (this.numberTicks != null && this.tickInterval != null) {
  307. nt = this.numberTicks;
  308. for (var i=0; i<nt; i++) {
  309. opts.value = this.min + i * this.tickInterval;
  310. t = new this.tickRenderer(opts);
  311. if (this._overrideFormatString && this._autoFormatString != '') {
  312. t.formatString = this._autoFormatString;
  313. }
  314. if (!this.showTicks) {
  315. t.showLabel = false;
  316. t.showMark = false;
  317. }
  318. else if (!this.showTickMarks) {
  319. t.showMark = false;
  320. }
  321. this._ticks.push(t);
  322. }
  323. }
  324. }
  325. };
  326. $.jqplot.LogAxisRenderer.prototype.pack = function(pos, offsets) {
  327. var lb = parseInt(this.base, 10);
  328. var ticks = this._ticks;
  329. var trans = function (v) { return Math.log(v)/Math.log(lb); };
  330. var invtrans = function (v) { return Math.pow(Math.E, (Math.log(lb)*v)); };
  331. var max = trans(this.max);
  332. var min = trans(this.min);
  333. var offmax = offsets.max;
  334. var offmin = offsets.min;
  335. var lshow = (this._label == null) ? false : this._label.show;
  336. for (var p in pos) {
  337. this._elem.css(p, pos[p]);
  338. }
  339. this._offsets = offsets;
  340. // pixellength will be + for x axes and - for y axes becasue pixels always measured from top left.
  341. var pixellength = offmax - offmin;
  342. var unitlength = max - min;
  343. // point to unit and unit to point conversions references to Plot DOM element top left corner.
  344. this.p2u = function(p){
  345. return invtrans((p - offmin) * unitlength / pixellength + min);
  346. };
  347. this.u2p = function(u){
  348. return (trans(u) - min) * pixellength / unitlength + offmin;
  349. };
  350. if (this.name == 'xaxis' || this.name == 'x2axis'){
  351. this.series_u2p = function(u){
  352. return (trans(u) - min) * pixellength / unitlength;
  353. };
  354. this.series_p2u = function(p){
  355. return invtrans(p * unitlength / pixellength + min);
  356. };
  357. }
  358. // yaxis is max at top of canvas.
  359. else {
  360. this.series_u2p = function(u){
  361. return (trans(u) - max) * pixellength / unitlength;
  362. };
  363. this.series_p2u = function(p){
  364. return invtrans(p * unitlength / pixellength + max);
  365. };
  366. }
  367. if (this.show) {
  368. if (this.name == 'xaxis' || this.name == 'x2axis') {
  369. for (var i=0; i<ticks.length; i++) {
  370. var t = ticks[i];
  371. if (t.show && t.showLabel) {
  372. var shim;
  373. if (t.constructor == $.jqplot.CanvasAxisTickRenderer && t.angle) {
  374. switch (t.labelPosition) {
  375. case 'auto':
  376. // position at end
  377. if (t.angle < 0) {
  378. shim = -t.getWidth() + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
  379. }
  380. // position at start
  381. else {
  382. shim = -t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
  383. }
  384. break;
  385. case 'end':
  386. shim = -t.getWidth() + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
  387. break;
  388. case 'start':
  389. shim = -t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
  390. break;
  391. case 'middle':
  392. shim = -t.getWidth()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
  393. break;
  394. default:
  395. shim = -t.getWidth()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
  396. break;
  397. }
  398. }
  399. else {
  400. shim = -t.getWidth()/2;
  401. }
  402. // var shim = t.getWidth()/2;
  403. var val = this.u2p(t.value) + shim + 'px';
  404. t._elem.css('left', val);
  405. t.pack();
  406. }
  407. }
  408. if (lshow) {
  409. var w = this._label._elem.outerWidth(true);
  410. this._label._elem.css('left', offmin + pixellength/2 - w/2 + 'px');
  411. if (this.name == 'xaxis') {
  412. this._label._elem.css('bottom', '0px');
  413. }
  414. else {
  415. this._label._elem.css('top', '0px');
  416. }
  417. this._label.pack();
  418. }
  419. }
  420. else {
  421. for (var i=0; i<ticks.length; i++) {
  422. var t = ticks[i];
  423. if (t.show && t.showLabel) {
  424. var shim;
  425. if (t.constructor == $.jqplot.CanvasAxisTickRenderer && t.angle) {
  426. switch (t.labelPosition) {
  427. case 'auto':
  428. // position at end
  429. case 'end':
  430. if (t.angle < 0) {
  431. shim = -t._textRenderer.height * Math.cos(-t._textRenderer.angle) / 2;
  432. }
  433. else {
  434. shim = -t.getHeight() + t._textRenderer.height * Math.cos(t._textRenderer.angle) / 2;
  435. }
  436. break;
  437. case 'start':
  438. if (t.angle > 0) {
  439. shim = -t._textRenderer.height * Math.cos(-t._textRenderer.angle) / 2;
  440. }
  441. else {
  442. shim = -t.getHeight() + t._textRenderer.height * Math.cos(t._textRenderer.angle) / 2;
  443. }
  444. break;
  445. case 'middle':
  446. // if (t.angle > 0) {
  447. // shim = -t.getHeight()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
  448. // }
  449. // else {
  450. // shim = -t.getHeight()/2 - t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
  451. // }
  452. shim = -t.getHeight()/2;
  453. break;
  454. default:
  455. shim = -t.getHeight()/2;
  456. break;
  457. }
  458. }
  459. else {
  460. shim = -t.getHeight()/2;
  461. }
  462. var val = this.u2p(t.value) + shim + 'px';
  463. t._elem.css('top', val);
  464. t.pack();
  465. }
  466. }
  467. if (lshow) {
  468. var h = this._label._elem.outerHeight(true);
  469. this._label._elem.css('top', offmax - pixellength/2 - h/2 + 'px');
  470. if (this.name == 'yaxis') {
  471. this._label._elem.css('left', '0px');
  472. }
  473. else {
  474. this._label._elem.css('right', '0px');
  475. }
  476. this._label.pack();
  477. }
  478. }
  479. }
  480. };
  481. })(jQuery);