jqplot.linearAxisRenderer.js 44 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006
  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.LinearAxisRenderer
  33. // The default jqPlot axis renderer, creating a numeric axis.
  34. $.jqplot.LinearAxisRenderer = function() {
  35. };
  36. // called with scope of axis object.
  37. $.jqplot.LinearAxisRenderer.prototype.init = function(options){
  38. // prop: breakPoints
  39. // EXPERIMENTAL!! Use at your own risk!
  40. // Works only with linear axes and the default tick renderer.
  41. // Array of [start, stop] points to create a broken axis.
  42. // Broken axes have a "jump" in them, which is an immediate
  43. // transition from a smaller value to a larger value.
  44. // Currently, axis ticks MUST be manually assigned if using breakPoints
  45. // by using the axis ticks array option.
  46. this.breakPoints = null;
  47. // prop: breakTickLabel
  48. // Label to use at the axis break if breakPoints are specified.
  49. this.breakTickLabel = "≈";
  50. // prop: drawBaseline
  51. // True to draw the axis baseline.
  52. this.drawBaseline = true;
  53. // prop: baselineWidth
  54. // width of the baseline in pixels.
  55. this.baselineWidth = null;
  56. // prop: baselineColor
  57. // CSS color spec for the baseline.
  58. this.baselineColor = null;
  59. // prop: forceTickAt0
  60. // This will ensure that there is always a tick mark at 0.
  61. // If data range is strictly positive or negative,
  62. // this will force 0 to be inside the axis bounds unless
  63. // the appropriate axis pad (pad, padMin or padMax) is set
  64. // to 0, then this will force an axis min or max value at 0.
  65. // This has know effect when any of the following options
  66. // are set: autoscale, min, max, numberTicks or tickInterval.
  67. this.forceTickAt0 = false;
  68. // prop: forceTickAt100
  69. // This will ensure that there is always a tick mark at 100.
  70. // If data range is strictly above or below 100,
  71. // this will force 100 to be inside the axis bounds unless
  72. // the appropriate axis pad (pad, padMin or padMax) is set
  73. // to 0, then this will force an axis min or max value at 100.
  74. // This has know effect when any of the following options
  75. // are set: autoscale, min, max, numberTicks or tickInterval.
  76. this.forceTickAt100 = false;
  77. // prop: tickInset
  78. // Controls the amount to inset the first and last ticks from
  79. // the edges of the grid, in multiples of the tick interval.
  80. // 0 is no inset, 0.5 is one half a tick interval, 1 is a full
  81. // tick interval, etc.
  82. this.tickInset = 0;
  83. // prop: minorTicks
  84. // Number of ticks to add between "major" ticks.
  85. // Major ticks are ticks supplied by user or auto computed.
  86. // Minor ticks cannot be created by user.
  87. this.minorTicks = 0;
  88. // prop: alignTicks
  89. // true to align tick marks across opposed axes
  90. // such as from the y2axis to yaxis.
  91. this.alignTicks = false;
  92. this._autoFormatString = '';
  93. this._overrideFormatString = false;
  94. this._scalefact = 1.0;
  95. $.extend(true, this, options);
  96. if (this.breakPoints) {
  97. if (!$.isArray(this.breakPoints)) {
  98. this.breakPoints = null;
  99. }
  100. else if (this.breakPoints.length < 2 || this.breakPoints[1] <= this.breakPoints[0]) {
  101. this.breakPoints = null;
  102. }
  103. }
  104. if (this.numberTicks != null && this.numberTicks < 2) {
  105. this.numberTicks = 2;
  106. }
  107. this.resetDataBounds();
  108. };
  109. // called with scope of axis
  110. $.jqplot.LinearAxisRenderer.prototype.draw = function(ctx, plot) {
  111. if (this.show) {
  112. // populate the axis label and value properties.
  113. // createTicks is a method on the renderer, but
  114. // call it within the scope of the axis.
  115. this.renderer.createTicks.call(this, plot);
  116. // fill a div with axes labels in the right direction.
  117. // Need to pregenerate each axis to get its bounds and
  118. // position it and the labels correctly on the plot.
  119. var dim=0;
  120. var temp;
  121. // Added for theming.
  122. if (this._elem) {
  123. // Memory Leaks patch
  124. //this._elem.empty();
  125. this._elem.emptyForce();
  126. this._elem = null;
  127. }
  128. this._elem = $(document.createElement('div'));
  129. this._elem.addClass('jqplot-axis jqplot-'+this.name);
  130. this._elem.css('position', 'absolute');
  131. if (this.name == 'xaxis' || this.name == 'x2axis') {
  132. this._elem.width(this._plotDimensions.width);
  133. }
  134. else {
  135. this._elem.height(this._plotDimensions.height);
  136. }
  137. // create a _label object.
  138. this.labelOptions.axis = this.name;
  139. this._label = new this.labelRenderer(this.labelOptions);
  140. if (this._label.show) {
  141. var elem = this._label.draw(ctx, plot);
  142. elem.appendTo(this._elem);
  143. elem = null;
  144. }
  145. var t = this._ticks;
  146. var tick;
  147. for (var i=0; i<t.length; i++) {
  148. tick = t[i];
  149. if (tick.show && tick.showLabel && (!tick.isMinorTick || this.showMinorTicks)) {
  150. this._elem.append(tick.draw(ctx, plot));
  151. }
  152. }
  153. tick = null;
  154. t = null;
  155. }
  156. return this._elem;
  157. };
  158. // called with scope of an axis
  159. $.jqplot.LinearAxisRenderer.prototype.reset = function() {
  160. this.min = this._options.min;
  161. this.max = this._options.max;
  162. this.tickInterval = this._options.tickInterval;
  163. this.numberTicks = this._options.numberTicks;
  164. this._autoFormatString = '';
  165. if (this._overrideFormatString && this.tickOptions && this.tickOptions.formatString) {
  166. this.tickOptions.formatString = '';
  167. }
  168. // this._ticks = this.__ticks;
  169. };
  170. // called with scope of axis
  171. $.jqplot.LinearAxisRenderer.prototype.set = function() {
  172. var dim = 0;
  173. var temp;
  174. var w = 0;
  175. var h = 0;
  176. var lshow = (this._label == null) ? false : this._label.show;
  177. if (this.show) {
  178. var t = this._ticks;
  179. var tick;
  180. for (var i=0; i<t.length; i++) {
  181. tick = t[i];
  182. if (!tick._breakTick && tick.show && tick.showLabel && (!tick.isMinorTick || this.showMinorTicks)) {
  183. if (this.name == 'xaxis' || this.name == 'x2axis') {
  184. temp = tick._elem.outerHeight(true);
  185. }
  186. else {
  187. temp = tick._elem.outerWidth(true);
  188. }
  189. if (temp > dim) {
  190. dim = temp;
  191. }
  192. }
  193. }
  194. tick = null;
  195. t = null;
  196. if (lshow) {
  197. w = this._label._elem.outerWidth(true);
  198. h = this._label._elem.outerHeight(true);
  199. }
  200. if (this.name == 'xaxis') {
  201. dim = dim + h;
  202. this._elem.css({'height':dim+'px', left:'0px', bottom:'0px'});
  203. }
  204. else if (this.name == 'x2axis') {
  205. dim = dim + h;
  206. this._elem.css({'height':dim+'px', left:'0px', top:'0px'});
  207. }
  208. else if (this.name == 'yaxis') {
  209. dim = dim + w;
  210. this._elem.css({'width':dim+'px', left:'0px', top:'0px'});
  211. if (lshow && this._label.constructor == $.jqplot.AxisLabelRenderer) {
  212. this._label._elem.css('width', w+'px');
  213. }
  214. }
  215. else {
  216. dim = dim + w;
  217. this._elem.css({'width':dim+'px', right:'0px', top:'0px'});
  218. if (lshow && this._label.constructor == $.jqplot.AxisLabelRenderer) {
  219. this._label._elem.css('width', w+'px');
  220. }
  221. }
  222. }
  223. };
  224. // called with scope of axis
  225. $.jqplot.LinearAxisRenderer.prototype.createTicks = function(plot) {
  226. // we're are operating on an axis here
  227. var ticks = this._ticks;
  228. var userTicks = this.ticks;
  229. var name = this.name;
  230. // databounds were set on axis initialization.
  231. var db = this._dataBounds;
  232. var dim = (this.name.charAt(0) === 'x') ? this._plotDimensions.width : this._plotDimensions.height;
  233. var interval;
  234. var min, max;
  235. var pos1, pos2;
  236. var tt, i;
  237. // get a copy of user's settings for min/max.
  238. var userMin = this.min;
  239. var userMax = this.max;
  240. var userNT = this.numberTicks;
  241. var userTI = this.tickInterval;
  242. var threshold = 30;
  243. this._scalefact = (Math.max(dim, threshold+1) - threshold)/300.0;
  244. // if we already have ticks, use them.
  245. // ticks must be in order of increasing value.
  246. if (userTicks.length) {
  247. // ticks could be 1D or 2D array of [val, val, ,,,] or [[val, label], [val, label], ...] or mixed
  248. for (i=0; i<userTicks.length; i++){
  249. var ut = userTicks[i];
  250. var t = new this.tickRenderer(this.tickOptions);
  251. if ($.isArray(ut)) {
  252. t.value = ut[0];
  253. if (this.breakPoints) {
  254. if (ut[0] == this.breakPoints[0]) {
  255. t.label = this.breakTickLabel;
  256. t._breakTick = true;
  257. t.showGridline = false;
  258. t.showMark = false;
  259. }
  260. else if (ut[0] > this.breakPoints[0] && ut[0] <= this.breakPoints[1]) {
  261. t.show = false;
  262. t.showGridline = false;
  263. t.label = ut[1];
  264. }
  265. else {
  266. t.label = ut[1];
  267. }
  268. }
  269. else {
  270. t.label = ut[1];
  271. }
  272. t.setTick(ut[0], this.name);
  273. this._ticks.push(t);
  274. }
  275. else if ($.isPlainObject(ut)) {
  276. $.extend(true, t, ut);
  277. t.axis = this.name;
  278. this._ticks.push(t);
  279. }
  280. else {
  281. t.value = ut;
  282. if (this.breakPoints) {
  283. if (ut == this.breakPoints[0]) {
  284. t.label = this.breakTickLabel;
  285. t._breakTick = true;
  286. t.showGridline = false;
  287. t.showMark = false;
  288. }
  289. else if (ut > this.breakPoints[0] && ut <= this.breakPoints[1]) {
  290. t.show = false;
  291. t.showGridline = false;
  292. }
  293. }
  294. t.setTick(ut, this.name);
  295. this._ticks.push(t);
  296. }
  297. }
  298. this.numberTicks = userTicks.length;
  299. this.min = this._ticks[0].value;
  300. this.max = this._ticks[this.numberTicks-1].value;
  301. this.tickInterval = (this.max - this.min) / (this.numberTicks - 1);
  302. }
  303. // we don't have any ticks yet, let's make some!
  304. else {
  305. if (name == 'xaxis' || name == 'x2axis') {
  306. dim = this._plotDimensions.width;
  307. }
  308. else {
  309. dim = this._plotDimensions.height;
  310. }
  311. var _numberTicks = this.numberTicks;
  312. // if aligning this axis, use number of ticks from previous axis.
  313. // Do I need to reset somehow if alignTicks is changed and then graph is replotted??
  314. if (this.alignTicks) {
  315. if (this.name === 'x2axis' && plot.axes.xaxis.show) {
  316. _numberTicks = plot.axes.xaxis.numberTicks;
  317. }
  318. else if (this.name.charAt(0) === 'y' && this.name !== 'yaxis' && this.name !== 'yMidAxis' && plot.axes.yaxis.show) {
  319. _numberTicks = plot.axes.yaxis.numberTicks;
  320. }
  321. }
  322. min = ((this.min != null) ? this.min : db.min);
  323. max = ((this.max != null) ? this.max : db.max);
  324. var range = max - min;
  325. var rmin, rmax;
  326. var temp;
  327. if (this.tickOptions == null || !this.tickOptions.formatString) {
  328. this._overrideFormatString = true;
  329. }
  330. // Doing complete autoscaling
  331. if (this.min == null || this.max == null && this.tickInterval == null && !this.autoscale) {
  332. // Check if user must have tick at 0 or 100 and ensure they are in range.
  333. // The autoscaling algorithm will always place ticks at 0 and 100 if they are in range.
  334. if (this.forceTickAt0) {
  335. if (min > 0) {
  336. min = 0;
  337. }
  338. if (max < 0) {
  339. max = 0;
  340. }
  341. }
  342. if (this.forceTickAt100) {
  343. if (min > 100) {
  344. min = 100;
  345. }
  346. if (max < 100) {
  347. max = 100;
  348. }
  349. }
  350. var keepMin = false,
  351. keepMax = false;
  352. if (this.min != null) {
  353. keepMin = true;
  354. }
  355. else if (this.max != null) {
  356. keepMax = true;
  357. }
  358. // var threshold = 30;
  359. // var tdim = Math.max(dim, threshold+1);
  360. // this._scalefact = (tdim-threshold)/300.0;
  361. var ret = $.jqplot.LinearTickGenerator(min, max, this._scalefact, _numberTicks, keepMin, keepMax);
  362. // calculate a padded max and min, points should be less than these
  363. // so that they aren't too close to the edges of the plot.
  364. // User can adjust how much padding is allowed with pad, padMin and PadMax options.
  365. // If min or max is set, don't pad that end of axis.
  366. var tumin = (this.min != null) ? min : min + range*(this.padMin - 1);
  367. var tumax = (this.max != null) ? max : max - range*(this.padMax - 1);
  368. // if they're equal, we shouldn't have to do anything, right?
  369. // if (min <=tumin || max >= tumax) {
  370. if (min <tumin || max > tumax) {
  371. tumin = (this.min != null) ? min : min - range*(this.padMin - 1);
  372. tumax = (this.max != null) ? max : max + range*(this.padMax - 1);
  373. ret = $.jqplot.LinearTickGenerator(tumin, tumax, this._scalefact, _numberTicks, keepMin, keepMax);
  374. }
  375. this.min = ret[0];
  376. this.max = ret[1];
  377. // if numberTicks specified, it should return the same.
  378. this.numberTicks = ret[2];
  379. this._autoFormatString = ret[3];
  380. this.tickInterval = ret[4];
  381. }
  382. // User has specified some axis scale related option, can use auto algorithm
  383. else {
  384. // if min and max are same, space them out a bit
  385. if (min == max) {
  386. var adj = 0.05;
  387. if (min > 0) {
  388. adj = Math.max(Math.log(min)/Math.LN10, 0.05);
  389. }
  390. min -= adj;
  391. max += adj;
  392. }
  393. // autoscale. Can't autoscale if min or max is supplied.
  394. // Will use numberTicks and tickInterval if supplied. Ticks
  395. // across multiple axes may not line up depending on how
  396. // bars are to be plotted.
  397. if (this.autoscale && this.min == null && this.max == null) {
  398. var rrange, ti, margin;
  399. var forceMinZero = false;
  400. var forceZeroLine = false;
  401. var intervals = {min:null, max:null, average:null, stddev:null};
  402. // if any series are bars, or if any are fill to zero, and if this
  403. // is the axis to fill toward, check to see if we can start axis at zero.
  404. for (var i=0; i<this._series.length; i++) {
  405. var s = this._series[i];
  406. var faname = (s.fillAxis == 'x') ? s._xaxis.name : s._yaxis.name;
  407. // check to see if this is the fill axis
  408. if (this.name == faname) {
  409. var vals = s._plotValues[s.fillAxis];
  410. var vmin = vals[0];
  411. var vmax = vals[0];
  412. for (var j=1; j<vals.length; j++) {
  413. if (vals[j] < vmin) {
  414. vmin = vals[j];
  415. }
  416. else if (vals[j] > vmax) {
  417. vmax = vals[j];
  418. }
  419. }
  420. var dp = (vmax - vmin) / vmax;
  421. // is this sries a bar?
  422. if (s.renderer.constructor == $.jqplot.BarRenderer) {
  423. // if no negative values and could also check range.
  424. if (vmin >= 0 && (s.fillToZero || dp > 0.1)) {
  425. forceMinZero = true;
  426. }
  427. else {
  428. forceMinZero = false;
  429. if (s.fill && s.fillToZero && vmin < 0 && vmax > 0) {
  430. forceZeroLine = true;
  431. }
  432. else {
  433. forceZeroLine = false;
  434. }
  435. }
  436. }
  437. // if not a bar and filling, use appropriate method.
  438. else if (s.fill) {
  439. if (vmin >= 0 && (s.fillToZero || dp > 0.1)) {
  440. forceMinZero = true;
  441. }
  442. else if (vmin < 0 && vmax > 0 && s.fillToZero) {
  443. forceMinZero = false;
  444. forceZeroLine = true;
  445. }
  446. else {
  447. forceMinZero = false;
  448. forceZeroLine = false;
  449. }
  450. }
  451. // if not a bar and not filling, only change existing state
  452. // if it doesn't make sense
  453. else if (vmin < 0) {
  454. forceMinZero = false;
  455. }
  456. }
  457. }
  458. // check if we need make axis min at 0.
  459. if (forceMinZero) {
  460. // compute number of ticks
  461. this.numberTicks = 2 + Math.ceil((dim-(this.tickSpacing-1))/this.tickSpacing);
  462. this.min = 0;
  463. userMin = 0;
  464. // what order is this range?
  465. // what tick interval does that give us?
  466. ti = max/(this.numberTicks-1);
  467. temp = Math.pow(10, Math.abs(Math.floor(Math.log(ti)/Math.LN10)));
  468. if (ti/temp == parseInt(ti/temp, 10)) {
  469. ti += temp;
  470. }
  471. this.tickInterval = Math.ceil(ti/temp) * temp;
  472. this.max = this.tickInterval * (this.numberTicks - 1);
  473. }
  474. // check if we need to make sure there is a tick at 0.
  475. else if (forceZeroLine) {
  476. // compute number of ticks
  477. this.numberTicks = 2 + Math.ceil((dim-(this.tickSpacing-1))/this.tickSpacing);
  478. var ntmin = Math.ceil(Math.abs(min)/range*(this.numberTicks-1));
  479. var ntmax = this.numberTicks - 1 - ntmin;
  480. ti = Math.max(Math.abs(min/ntmin), Math.abs(max/ntmax));
  481. temp = Math.pow(10, Math.abs(Math.floor(Math.log(ti)/Math.LN10)));
  482. this.tickInterval = Math.ceil(ti/temp) * temp;
  483. this.max = this.tickInterval * ntmax;
  484. this.min = -this.tickInterval * ntmin;
  485. }
  486. // if nothing else, do autoscaling which will try to line up ticks across axes.
  487. else {
  488. if (this.numberTicks == null){
  489. if (this.tickInterval) {
  490. this.numberTicks = 3 + Math.ceil(range / this.tickInterval);
  491. }
  492. else {
  493. this.numberTicks = 2 + Math.ceil((dim-(this.tickSpacing-1))/this.tickSpacing);
  494. }
  495. }
  496. if (this.tickInterval == null) {
  497. // get a tick interval
  498. ti = range/(this.numberTicks - 1);
  499. if (ti < 1) {
  500. temp = Math.pow(10, Math.abs(Math.floor(Math.log(ti)/Math.LN10)));
  501. }
  502. else {
  503. temp = 1;
  504. }
  505. this.tickInterval = Math.ceil(ti*temp*this.pad)/temp;
  506. }
  507. else {
  508. temp = 1 / this.tickInterval;
  509. }
  510. // try to compute a nicer, more even tick interval
  511. // temp = Math.pow(10, Math.floor(Math.log(ti)/Math.LN10));
  512. // this.tickInterval = Math.ceil(ti/temp) * temp;
  513. rrange = this.tickInterval * (this.numberTicks - 1);
  514. margin = (rrange - range)/2;
  515. if (this.min == null) {
  516. this.min = Math.floor(temp*(min-margin))/temp;
  517. }
  518. if (this.max == null) {
  519. this.max = this.min + rrange;
  520. }
  521. }
  522. // Compute a somewhat decent format string if it is needed.
  523. // get precision of interval and determine a format string.
  524. var sf = $.jqplot.getSignificantFigures(this.tickInterval);
  525. var fstr;
  526. // if we have only a whole number, use integer formatting
  527. if (sf.digitsLeft >= sf.significantDigits) {
  528. fstr = '%d';
  529. }
  530. else {
  531. var temp = Math.max(0, 5 - sf.digitsLeft);
  532. temp = Math.min(temp, sf.digitsRight);
  533. fstr = '%.'+ temp + 'f';
  534. }
  535. this._autoFormatString = fstr;
  536. }
  537. // Use the default algorithm which pads each axis to make the chart
  538. // centered nicely on the grid.
  539. else {
  540. rmin = (this.min != null) ? this.min : min - range*(this.padMin - 1);
  541. rmax = (this.max != null) ? this.max : max + range*(this.padMax - 1);
  542. range = rmax - rmin;
  543. if (this.numberTicks == null){
  544. // if tickInterval is specified by user, we will ignore computed maximum.
  545. // max will be equal or greater to fit even # of ticks.
  546. if (this.tickInterval != null) {
  547. this.numberTicks = Math.ceil((rmax - rmin)/this.tickInterval)+1;
  548. }
  549. else if (dim > 100) {
  550. this.numberTicks = parseInt(3+(dim-100)/75, 10);
  551. }
  552. else {
  553. this.numberTicks = 2;
  554. }
  555. }
  556. if (this.tickInterval == null) {
  557. this.tickInterval = range / (this.numberTicks-1);
  558. }
  559. if (this.max == null) {
  560. rmax = rmin + this.tickInterval*(this.numberTicks - 1);
  561. }
  562. if (this.min == null) {
  563. rmin = rmax - this.tickInterval*(this.numberTicks - 1);
  564. }
  565. // get precision of interval and determine a format string.
  566. var sf = $.jqplot.getSignificantFigures(this.tickInterval);
  567. var fstr;
  568. // if we have only a whole number, use integer formatting
  569. if (sf.digitsLeft >= sf.significantDigits) {
  570. fstr = '%d';
  571. }
  572. else {
  573. var temp = Math.max(0, 5 - sf.digitsLeft);
  574. temp = Math.min(temp, sf.digitsRight);
  575. fstr = '%.'+ temp + 'f';
  576. }
  577. this._autoFormatString = fstr;
  578. this.min = rmin;
  579. this.max = rmax;
  580. }
  581. if (this.renderer.constructor == $.jqplot.LinearAxisRenderer && this._autoFormatString == '') {
  582. // fix for misleading tick display with small range and low precision.
  583. range = this.max - this.min;
  584. // figure out precision
  585. var temptick = new this.tickRenderer(this.tickOptions);
  586. // use the tick formatString or, the default.
  587. var fs = temptick.formatString || $.jqplot.config.defaultTickFormatString;
  588. var fs = fs.match($.jqplot.sprintf.regex)[0];
  589. var precision = 0;
  590. if (fs) {
  591. if (fs.search(/[fFeEgGpP]/) > -1) {
  592. var m = fs.match(/\%\.(\d{0,})?[eEfFgGpP]/);
  593. if (m) {
  594. precision = parseInt(m[1], 10);
  595. }
  596. else {
  597. precision = 6;
  598. }
  599. }
  600. else if (fs.search(/[di]/) > -1) {
  601. precision = 0;
  602. }
  603. // fact will be <= 1;
  604. var fact = Math.pow(10, -precision);
  605. if (this.tickInterval < fact) {
  606. // need to correct underrange
  607. if (userNT == null && userTI == null) {
  608. this.tickInterval = fact;
  609. if (userMax == null && userMin == null) {
  610. // this.min = Math.floor((this._dataBounds.min - this.tickInterval)/fact) * fact;
  611. this.min = Math.floor(this._dataBounds.min/fact) * fact;
  612. if (this.min == this._dataBounds.min) {
  613. this.min = this._dataBounds.min - this.tickInterval;
  614. }
  615. // this.max = Math.ceil((this._dataBounds.max + this.tickInterval)/fact) * fact;
  616. this.max = Math.ceil(this._dataBounds.max/fact) * fact;
  617. if (this.max == this._dataBounds.max) {
  618. this.max = this._dataBounds.max + this.tickInterval;
  619. }
  620. var n = (this.max - this.min)/this.tickInterval;
  621. n = n.toFixed(11);
  622. n = Math.ceil(n);
  623. this.numberTicks = n + 1;
  624. }
  625. else if (userMax == null) {
  626. // add one tick for top of range.
  627. var n = (this._dataBounds.max - this.min) / this.tickInterval;
  628. n = n.toFixed(11);
  629. this.numberTicks = Math.ceil(n) + 2;
  630. this.max = this.min + this.tickInterval * (this.numberTicks-1);
  631. }
  632. else if (userMin == null) {
  633. // add one tick for bottom of range.
  634. var n = (this.max - this._dataBounds.min) / this.tickInterval;
  635. n = n.toFixed(11);
  636. this.numberTicks = Math.ceil(n) + 2;
  637. this.min = this.max - this.tickInterval * (this.numberTicks-1);
  638. }
  639. else {
  640. // calculate a number of ticks so max is within axis scale
  641. this.numberTicks = Math.ceil((userMax - userMin)/this.tickInterval) + 1;
  642. // if user's min and max don't fit evenly in ticks, adjust.
  643. // This takes care of cases such as user min set to 0, max set to 3.5 but tick
  644. // format string set to %d (integer ticks)
  645. this.min = Math.floor(userMin*Math.pow(10, precision))/Math.pow(10, precision);
  646. this.max = Math.ceil(userMax*Math.pow(10, precision))/Math.pow(10, precision);
  647. // this.max = this.min + this.tickInterval*(this.numberTicks-1);
  648. this.numberTicks = Math.ceil((this.max - this.min)/this.tickInterval) + 1;
  649. }
  650. }
  651. }
  652. }
  653. }
  654. }
  655. if (this._overrideFormatString && this._autoFormatString != '') {
  656. this.tickOptions = this.tickOptions || {};
  657. this.tickOptions.formatString = this._autoFormatString;
  658. }
  659. var t, to;
  660. for (var i=0; i<this.numberTicks; i++){
  661. tt = this.min + i * this.tickInterval;
  662. t = new this.tickRenderer(this.tickOptions);
  663. // var t = new $.jqplot.AxisTickRenderer(this.tickOptions);
  664. t.setTick(tt, this.name);
  665. this._ticks.push(t);
  666. if (i < this.numberTicks - 1) {
  667. for (var j=0; j<this.minorTicks; j++) {
  668. tt += this.tickInterval/(this.minorTicks+1);
  669. to = $.extend(true, {}, this.tickOptions, {name:this.name, value:tt, label:'', isMinorTick:true});
  670. t = new this.tickRenderer(to);
  671. this._ticks.push(t);
  672. }
  673. }
  674. t = null;
  675. }
  676. }
  677. if (this.tickInset) {
  678. this.min = this.min - this.tickInset * this.tickInterval;
  679. this.max = this.max + this.tickInset * this.tickInterval;
  680. }
  681. ticks = null;
  682. };
  683. // Used to reset just the values of the ticks and then repack, which will
  684. // recalculate the positioning functions. It is assuemd that the
  685. // number of ticks is the same and the values of the new array are at the
  686. // proper interval.
  687. // This method needs to be called with the scope of an axis object, like:
  688. //
  689. // > plot.axes.yaxis.renderer.resetTickValues.call(plot.axes.yaxis, yarr);
  690. //
  691. $.jqplot.LinearAxisRenderer.prototype.resetTickValues = function(opts) {
  692. if ($.isArray(opts) && opts.length == this._ticks.length) {
  693. var t;
  694. for (var i=0; i<opts.length; i++) {
  695. t = this._ticks[i];
  696. t.value = opts[i];
  697. t.label = t.formatter(t.formatString, opts[i]);
  698. t.label = t.prefix + t.label;
  699. t._elem.html(t.label);
  700. }
  701. t = null;
  702. this.min = $.jqplot.arrayMin(opts);
  703. this.max = $.jqplot.arrayMax(opts);
  704. this.pack();
  705. }
  706. // Not implemented yet.
  707. // else if ($.isPlainObject(opts)) {
  708. //
  709. // }
  710. };
  711. // called with scope of axis
  712. $.jqplot.LinearAxisRenderer.prototype.pack = function(pos, offsets) {
  713. // Add defaults for repacking from resetTickValues function.
  714. pos = pos || {};
  715. offsets = offsets || this._offsets;
  716. var ticks = this._ticks;
  717. var max = this.max;
  718. var min = this.min;
  719. var offmax = offsets.max;
  720. var offmin = offsets.min;
  721. var lshow = (this._label == null) ? false : this._label.show;
  722. for (var p in pos) {
  723. this._elem.css(p, pos[p]);
  724. }
  725. this._offsets = offsets;
  726. // pixellength will be + for x axes and - for y axes becasue pixels always measured from top left.
  727. var pixellength = offmax - offmin;
  728. var unitlength = max - min;
  729. // point to unit and unit to point conversions references to Plot DOM element top left corner.
  730. if (this.breakPoints) {
  731. unitlength = unitlength - this.breakPoints[1] + this.breakPoints[0];
  732. this.p2u = function(p){
  733. return (p - offmin) * unitlength / pixellength + min;
  734. };
  735. this.u2p = function(u){
  736. if (u > this.breakPoints[0] && u < this.breakPoints[1]){
  737. u = this.breakPoints[0];
  738. }
  739. if (u <= this.breakPoints[0]) {
  740. return (u - min) * pixellength / unitlength + offmin;
  741. }
  742. else {
  743. return (u - this.breakPoints[1] + this.breakPoints[0] - min) * pixellength / unitlength + offmin;
  744. }
  745. };
  746. if (this.name.charAt(0) == 'x'){
  747. this.series_u2p = function(u){
  748. if (u > this.breakPoints[0] && u < this.breakPoints[1]){
  749. u = this.breakPoints[0];
  750. }
  751. if (u <= this.breakPoints[0]) {
  752. return (u - min) * pixellength / unitlength;
  753. }
  754. else {
  755. return (u - this.breakPoints[1] + this.breakPoints[0] - min) * pixellength / unitlength;
  756. }
  757. };
  758. this.series_p2u = function(p){
  759. return p * unitlength / pixellength + min;
  760. };
  761. }
  762. else {
  763. this.series_u2p = function(u){
  764. if (u > this.breakPoints[0] && u < this.breakPoints[1]){
  765. u = this.breakPoints[0];
  766. }
  767. if (u >= this.breakPoints[1]) {
  768. return (u - max) * pixellength / unitlength;
  769. }
  770. else {
  771. return (u + this.breakPoints[1] - this.breakPoints[0] - max) * pixellength / unitlength;
  772. }
  773. };
  774. this.series_p2u = function(p){
  775. return p * unitlength / pixellength + max;
  776. };
  777. }
  778. }
  779. else {
  780. this.p2u = function(p){
  781. return (p - offmin) * unitlength / pixellength + min;
  782. };
  783. this.u2p = function(u){
  784. return (u - min) * pixellength / unitlength + offmin;
  785. };
  786. if (this.name == 'xaxis' || this.name == 'x2axis'){
  787. this.series_u2p = function(u){
  788. return (u - min) * pixellength / unitlength;
  789. };
  790. this.series_p2u = function(p){
  791. return p * unitlength / pixellength + min;
  792. };
  793. }
  794. else {
  795. this.series_u2p = function(u){
  796. return (u - max) * pixellength / unitlength;
  797. };
  798. this.series_p2u = function(p){
  799. return p * unitlength / pixellength + max;
  800. };
  801. }
  802. }
  803. if (this.show) {
  804. if (this.name == 'xaxis' || this.name == 'x2axis') {
  805. for (var i=0; i<ticks.length; i++) {
  806. var t = ticks[i];
  807. if (t.show && t.showLabel) {
  808. var shim;
  809. if (t.constructor == $.jqplot.CanvasAxisTickRenderer && t.angle) {
  810. // will need to adjust auto positioning based on which axis this is.
  811. var temp = (this.name == 'xaxis') ? 1 : -1;
  812. switch (t.labelPosition) {
  813. case 'auto':
  814. // position at end
  815. if (temp * t.angle < 0) {
  816. shim = -t.getWidth() + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
  817. }
  818. // position at start
  819. else {
  820. shim = -t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
  821. }
  822. break;
  823. case 'end':
  824. shim = -t.getWidth() + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
  825. break;
  826. case 'start':
  827. shim = -t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
  828. break;
  829. case 'middle':
  830. shim = -t.getWidth()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
  831. break;
  832. default:
  833. shim = -t.getWidth()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
  834. break;
  835. }
  836. }
  837. else {
  838. shim = -t.getWidth()/2;
  839. }
  840. var val = this.u2p(t.value) + shim + 'px';
  841. t._elem.css('left', val);
  842. t.pack();
  843. }
  844. }
  845. if (lshow) {
  846. var w = this._label._elem.outerWidth(true);
  847. this._label._elem.css('left', offmin + pixellength/2 - w/2 + 'px');
  848. if (this.name == 'xaxis') {
  849. this._label._elem.css('bottom', '0px');
  850. }
  851. else {
  852. this._label._elem.css('top', '0px');
  853. }
  854. this._label.pack();
  855. }
  856. }
  857. else {
  858. for (var i=0; i<ticks.length; i++) {
  859. var t = ticks[i];
  860. if (t.show && t.showLabel) {
  861. var shim;
  862. if (t.constructor == $.jqplot.CanvasAxisTickRenderer && t.angle) {
  863. // will need to adjust auto positioning based on which axis this is.
  864. var temp = (this.name == 'yaxis') ? 1 : -1;
  865. switch (t.labelPosition) {
  866. case 'auto':
  867. // position at end
  868. case 'end':
  869. if (temp * t.angle < 0) {
  870. shim = -t._textRenderer.height * Math.cos(-t._textRenderer.angle) / 2;
  871. }
  872. else {
  873. shim = -t.getHeight() + t._textRenderer.height * Math.cos(t._textRenderer.angle) / 2;
  874. }
  875. break;
  876. case 'start':
  877. if (t.angle > 0) {
  878. shim = -t._textRenderer.height * Math.cos(-t._textRenderer.angle) / 2;
  879. }
  880. else {
  881. shim = -t.getHeight() + t._textRenderer.height * Math.cos(t._textRenderer.angle) / 2;
  882. }
  883. break;
  884. case 'middle':
  885. // if (t.angle > 0) {
  886. // shim = -t.getHeight()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
  887. // }
  888. // else {
  889. // shim = -t.getHeight()/2 - t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
  890. // }
  891. shim = -t.getHeight()/2;
  892. break;
  893. default:
  894. shim = -t.getHeight()/2;
  895. break;
  896. }
  897. }
  898. else {
  899. shim = -t.getHeight()/2;
  900. }
  901. var val = this.u2p(t.value) + shim + 'px';
  902. t._elem.css('top', val);
  903. t.pack();
  904. }
  905. }
  906. if (lshow) {
  907. var h = this._label._elem.outerHeight(true);
  908. this._label._elem.css('top', offmax - pixellength/2 - h/2 + 'px');
  909. if (this.name == 'yaxis') {
  910. this._label._elem.css('left', '0px');
  911. }
  912. else {
  913. this._label._elem.css('right', '0px');
  914. }
  915. this._label.pack();
  916. }
  917. }
  918. }
  919. ticks = null;
  920. };
  921. })(jQuery);