jqplot.linearTickGenerator.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  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. * The following code was generaously given to me a while back by Scott Prahl.
  34. * He did a good job at computing axes min, max and number of ticks for the
  35. * case where the user has not set any scale related parameters (tickInterval,
  36. * numberTicks, min or max). I had ignored this use case for a long time,
  37. * focusing on the more difficult case where user has set some option controlling
  38. * tick generation. Anyway, about time I got this into jqPlot.
  39. * Thanks Scott!!
  40. */
  41. /**
  42. * Copyright (c) 2010 Scott Prahl
  43. * The next three routines are currently available for use in all personal
  44. * or commercial projects under both the MIT and GPL version 2.0 licenses.
  45. * This means that you can choose the license that best suits your project
  46. * and use it accordingly.
  47. */
  48. // A good format string depends on the interval. If the interval is greater
  49. // than 1 then there is no need to show any decimal digits. If it is < 1.0, then
  50. // use the magnitude of the interval to determine the number of digits to show.
  51. function bestFormatString (interval)
  52. {
  53. var fstr;
  54. interval = Math.abs(interval);
  55. if (interval >= 10) {
  56. fstr = '%d';
  57. }
  58. else if (interval > 1) {
  59. if (interval === parseInt(interval, 10)) {
  60. fstr = '%d';
  61. }
  62. else {
  63. fstr = '%.1f';
  64. }
  65. }
  66. else {
  67. var expv = -Math.floor(Math.log(interval)/Math.LN10);
  68. fstr = '%.' + expv + 'f';
  69. }
  70. return fstr;
  71. }
  72. var _factors = [0.1, 0.2, 0.3, 0.4, 0.5, 0.8, 1, 2, 3, 4, 5];
  73. var _getLowerFactor = function(f) {
  74. var i = _factors.indexOf(f);
  75. if (i > 0) {
  76. return _factors[i-1];
  77. }
  78. else {
  79. return _factors[_factors.length - 1] / 100;
  80. }
  81. };
  82. var _getHigherFactor = function(f) {
  83. var i = _factors.indexOf(f);
  84. if (i < _factors.length-1) {
  85. return _factors[i+1];
  86. }
  87. else {
  88. return _factors[0] * 100;
  89. }
  90. };
  91. // Given a fixed minimum and maximum and a target number ot ticks
  92. // figure out the best interval and
  93. // return min, max, number ticks, format string and tick interval
  94. function bestConstrainedInterval(min, max, nttarget) {
  95. // run through possible number to ticks and see which interval is best
  96. var low = Math.floor(nttarget/2);
  97. var hi = Math.ceil(nttarget*1.5);
  98. var badness = Number.MAX_VALUE;
  99. var r = (max - min);
  100. var temp;
  101. var sd;
  102. var bestNT;
  103. var gsf = $.jqplot.getSignificantFigures;
  104. var fsd;
  105. var fs;
  106. var currentNT;
  107. var bestPrec;
  108. for (var i=0, l=hi-low+1; i<l; i++) {
  109. currentNT = low + i;
  110. temp = r/(currentNT-1);
  111. sd = gsf(temp);
  112. temp = Math.abs(nttarget - currentNT) + sd.digitsRight;
  113. if (temp < badness) {
  114. badness = temp;
  115. bestNT = currentNT;
  116. bestPrec = sd.digitsRight;
  117. }
  118. else if (temp === badness) {
  119. // let nicer ticks trump number ot ticks
  120. if (sd.digitsRight < bestPrec) {
  121. bestNT = currentNT;
  122. bestPrec = sd.digitsRight;
  123. }
  124. }
  125. }
  126. fsd = Math.max(bestPrec, Math.max(gsf(min).digitsRight, gsf(max).digitsRight));
  127. if (fsd === 0) {
  128. fs = '%d';
  129. }
  130. else {
  131. fs = '%.' + fsd + 'f';
  132. }
  133. temp = r / (bestNT - 1);
  134. // min, max, number ticks, format string, tick interval
  135. return [min, max, bestNT, fs, temp];
  136. }
  137. // This will return an interval of form 2 * 10^n, 5 * 10^n or 10 * 10^n
  138. // it is based soley on the range and number of ticks. So if user specifies
  139. // number of ticks, use this.
  140. function bestInterval(range, numberTicks) {
  141. numberTicks = numberTicks || 7;
  142. var minimum = range / (numberTicks - 1);
  143. var magnitude = Math.pow(10, Math.floor(Math.log(minimum) / Math.LN10));
  144. var residual = minimum / magnitude;
  145. var interval;
  146. // "nicest" ranges are 1, 2, 5 or powers of these.
  147. // for magnitudes below 1, only allow these.
  148. if (magnitude < 1) {
  149. if (residual > 5) {
  150. interval = 10 * magnitude;
  151. }
  152. else if (residual > 2) {
  153. interval = 5 * magnitude;
  154. }
  155. else if (residual > 1) {
  156. interval = 2 * magnitude;
  157. }
  158. else {
  159. interval = magnitude;
  160. }
  161. }
  162. // for large ranges (whole integers), allow intervals like 3, 4 or powers of these.
  163. // this helps a lot with poor choices for number of ticks.
  164. else {
  165. if (residual > 5) {
  166. interval = 10 * magnitude;
  167. }
  168. else if (residual > 4) {
  169. interval = 5 * magnitude;
  170. }
  171. else if (residual > 3) {
  172. interval = 4 * magnitude;
  173. }
  174. else if (residual > 2) {
  175. interval = 3 * magnitude;
  176. }
  177. else if (residual > 1) {
  178. interval = 2 * magnitude;
  179. }
  180. else {
  181. interval = magnitude;
  182. }
  183. }
  184. return interval;
  185. }
  186. // This will return an interval of form 2 * 10^n, 5 * 10^n or 10 * 10^n
  187. // it is based soley on the range of data, number of ticks must be computed later.
  188. function bestLinearInterval(range, scalefact) {
  189. scalefact = scalefact || 1;
  190. var expv = Math.floor(Math.log(range)/Math.LN10);
  191. var magnitude = Math.pow(10, expv);
  192. // 0 < f < 10
  193. var f = range / magnitude;
  194. var fact;
  195. // for large plots, scalefact will decrease f and increase number of ticks.
  196. // for small plots, scalefact will increase f and decrease number of ticks.
  197. f = f/scalefact;
  198. // for large plots, smaller interval, more ticks.
  199. if (f<=0.38) {
  200. fact = 0.1;
  201. }
  202. else if (f<=1.6) {
  203. fact = 0.2;
  204. }
  205. else if (f<=4.0) {
  206. fact = 0.5;
  207. }
  208. else if (f<=8.0) {
  209. fact = 1.0;
  210. }
  211. // for very small plots, larger interval, less ticks in number ticks
  212. else if (f<=16.0) {
  213. fact = 2;
  214. }
  215. else {
  216. fact = 5;
  217. }
  218. return fact*magnitude;
  219. }
  220. function bestLinearComponents(range, scalefact) {
  221. var expv = Math.floor(Math.log(range)/Math.LN10);
  222. var magnitude = Math.pow(10, expv);
  223. // 0 < f < 10
  224. var f = range / magnitude;
  225. var interval;
  226. var fact;
  227. // for large plots, scalefact will decrease f and increase number of ticks.
  228. // for small plots, scalefact will increase f and decrease number of ticks.
  229. f = f/scalefact;
  230. // for large plots, smaller interval, more ticks.
  231. if (f<=0.38) {
  232. fact = 0.1;
  233. }
  234. else if (f<=1.6) {
  235. fact = 0.2;
  236. }
  237. else if (f<=4.0) {
  238. fact = 0.5;
  239. }
  240. else if (f<=8.0) {
  241. fact = 1.0;
  242. }
  243. // for very small plots, larger interval, less ticks in number ticks
  244. else if (f<=16.0) {
  245. fact = 2;
  246. }
  247. // else if (f<=20.0) {
  248. // fact = 3;
  249. // }
  250. // else if (f<=24.0) {
  251. // fact = 4;
  252. // }
  253. else {
  254. fact = 5;
  255. }
  256. interval = fact * magnitude;
  257. return [interval, fact, magnitude];
  258. }
  259. // Given the min and max for a dataset, return suitable endpoints
  260. // for the graphing, a good number for the number of ticks, and a
  261. // format string so that extraneous digits are not displayed.
  262. // returned is an array containing [min, max, nTicks, format]
  263. $.jqplot.LinearTickGenerator = function(axis_min, axis_max, scalefact, numberTicks, keepMin, keepMax) {
  264. // Set to preserve EITHER min OR max.
  265. // If min is preserved, max must be free.
  266. keepMin = (keepMin === null) ? false : keepMin;
  267. keepMax = (keepMax === null || keepMin) ? false : keepMax;
  268. // if endpoints are equal try to include zero otherwise include one
  269. if (axis_min === axis_max) {
  270. axis_max = (axis_max) ? 0 : 1;
  271. }
  272. scalefact = scalefact || 1.0;
  273. // make sure range is positive
  274. if (axis_max < axis_min) {
  275. var a = axis_max;
  276. axis_max = axis_min;
  277. axis_min = a;
  278. }
  279. var r = [];
  280. var ss = bestLinearInterval(axis_max - axis_min, scalefact);
  281. var gsf = $.jqplot.getSignificantFigures;
  282. if (numberTicks == null) {
  283. // Figure out the axis min, max and number of ticks
  284. // the min and max will be some multiple of the tick interval,
  285. // 1*10^n, 2*10^n or 5*10^n. This gaurantees that, if the
  286. // axis min is negative, 0 will be a tick.
  287. if (!keepMin && !keepMax) {
  288. r[0] = Math.floor(axis_min / ss) * ss; // min
  289. r[1] = Math.ceil(axis_max / ss) * ss; // max
  290. r[2] = Math.round((r[1]-r[0])/ss+1.0); // number of ticks
  291. r[3] = bestFormatString(ss); // format string
  292. r[4] = ss; // tick Interval
  293. }
  294. else if (keepMin) {
  295. r[0] = axis_min; // min
  296. r[2] = Math.ceil((axis_max - axis_min) / ss + 1.0); // number of ticks
  297. r[1] = axis_min + (r[2] - 1) * ss; // max
  298. var digitsMin = gsf(axis_min).digitsRight;
  299. var digitsSS = gsf(ss).digitsRight;
  300. if (digitsMin < digitsSS) {
  301. r[3] = bestFormatString(ss); // format string
  302. }
  303. else {
  304. r[3] = '%.' + digitsMin + 'f';
  305. }
  306. r[4] = ss; // tick Interval
  307. }
  308. else if (keepMax) {
  309. r[1] = axis_max; // max
  310. r[2] = Math.ceil((axis_max - axis_min) / ss + 1.0); // number of ticks
  311. r[0] = axis_max - (r[2] - 1) * ss; // min
  312. var digitsMax = gsf(axis_max).digitsRight;
  313. var digitsSS = gsf(ss).digitsRight;
  314. if (digitsMax < digitsSS) {
  315. r[3] = bestFormatString(ss); // format string
  316. }
  317. else {
  318. r[3] = '%.' + digitsMax + 'f';
  319. }
  320. r[4] = ss; // tick Interval
  321. }
  322. }
  323. else {
  324. var tempr = [];
  325. // Figure out the axis min, max and number of ticks
  326. // the min and max will be some multiple of the tick interval,
  327. // 1*10^n, 2*10^n or 5*10^n. This gaurantees that, if the
  328. // axis min is negative, 0 will be a tick.
  329. tempr[0] = Math.floor(axis_min / ss) * ss; // min
  330. tempr[1] = Math.ceil(axis_max / ss) * ss; // max
  331. tempr[2] = Math.round((tempr[1]-tempr[0])/ss+1.0); // number of ticks
  332. tempr[3] = bestFormatString(ss); // format string
  333. tempr[4] = ss; // tick Interval
  334. // first, see if we happen to get the right number of ticks
  335. if (tempr[2] === numberTicks) {
  336. r = tempr;
  337. }
  338. else {
  339. var newti = bestInterval(tempr[1] - tempr[0], numberTicks);
  340. r[0] = tempr[0]; // min
  341. r[2] = numberTicks; // number of ticks
  342. r[4] = newti; // tick interval
  343. r[3] = bestFormatString(newti); // format string
  344. r[1] = r[0] + (r[2] - 1) * r[4]; // max
  345. }
  346. }
  347. return r;
  348. };
  349. $.jqplot.LinearTickGenerator.bestLinearInterval = bestLinearInterval;
  350. $.jqplot.LinearTickGenerator.bestInterval = bestInterval;
  351. $.jqplot.LinearTickGenerator.bestLinearComponents = bestLinearComponents;
  352. $.jqplot.LinearTickGenerator.bestConstrainedInterval = bestConstrainedInterval;
  353. })(jQuery);