jquery.flot.logaxis.js 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. /* Pretty handling of log axes.
  2. Copyright (c) 2007-2014 IOLA and Ole Laursen.
  3. Copyright (c) 2015 Ciprian Ceteras cipix2000@gmail.com.
  4. Copyright (c) 2017 Raluca Portase
  5. Licensed under the MIT license.
  6. Set axis.mode to "log" to enable.
  7. */
  8. /* global jQuery*/
  9. /**
  10. ## jquery.flot.logaxis
  11. This plugin is used to create logarithmic axis. This includes tick generation,
  12. formatters and transformers to and from logarithmic representation.
  13. ### Methods and hooks
  14. */
  15. (function ($) {
  16. 'use strict';
  17. var options = {
  18. xaxis: {}
  19. };
  20. /*tick generators and formatters*/
  21. var PREFERRED_LOG_TICK_VALUES = computePreferedLogTickValues(Number.MAX_VALUE, 10),
  22. EXTENDED_LOG_TICK_VALUES = computePreferedLogTickValues(Number.MAX_VALUE, 4);
  23. function computePreferedLogTickValues(endLimit, rangeStep) {
  24. var log10End = Math.floor(Math.log(endLimit) * Math.LOG10E) - 1,
  25. log10Start = -log10End,
  26. val, range, vals = [];
  27. for (var power = log10Start; power <= log10End; power++) {
  28. range = Math.pow(10, power);
  29. for (var mult = 1; mult < 9; mult += rangeStep) {
  30. val = range * mult;
  31. vals.push(val);
  32. }
  33. }
  34. return vals;
  35. }
  36. /**
  37. - logTickGenerator(plot, axis, noTicks)
  38. Generates logarithmic ticks, depending on axis range.
  39. In case the number of ticks that can be generated is less than the expected noTicks/4,
  40. a linear tick generation is used.
  41. */
  42. var logTickGenerator = function (plot, axis, noTicks) {
  43. var ticks = [],
  44. minIdx = -1,
  45. maxIdx = -1,
  46. surface = plot.getCanvas(),
  47. logTickValues = PREFERRED_LOG_TICK_VALUES,
  48. min = clampAxis(axis, plot),
  49. max = axis.max;
  50. if (!noTicks) {
  51. noTicks = 0.3 * Math.sqrt(axis.direction === "x" ? surface.width : surface.height);
  52. }
  53. PREFERRED_LOG_TICK_VALUES.some(function (val, i) {
  54. if (val >= min) {
  55. minIdx = i;
  56. return true;
  57. } else {
  58. return false;
  59. }
  60. });
  61. PREFERRED_LOG_TICK_VALUES.some(function (val, i) {
  62. if (val >= max) {
  63. maxIdx = i;
  64. return true;
  65. } else {
  66. return false;
  67. }
  68. });
  69. if (maxIdx === -1) {
  70. maxIdx = PREFERRED_LOG_TICK_VALUES.length - 1;
  71. }
  72. if (maxIdx - minIdx <= noTicks / 4 && logTickValues.length !== EXTENDED_LOG_TICK_VALUES.length) {
  73. //try with multiple of 5 for tick values
  74. logTickValues = EXTENDED_LOG_TICK_VALUES;
  75. minIdx *= 2;
  76. maxIdx *= 2;
  77. }
  78. var lastDisplayed = null,
  79. inverseNoTicks = 1 / noTicks,
  80. tickValue, pixelCoord, tick;
  81. // Count the number of tick values would appear, if we can get at least
  82. // nTicks / 4 accept them.
  83. if (maxIdx - minIdx >= noTicks / 4) {
  84. for (var idx = maxIdx; idx >= minIdx; idx--) {
  85. tickValue = logTickValues[idx];
  86. pixelCoord = (Math.log(tickValue) - Math.log(min)) / (Math.log(max) - Math.log(min));
  87. tick = tickValue;
  88. if (lastDisplayed === null) {
  89. lastDisplayed = {
  90. pixelCoord: pixelCoord,
  91. idealPixelCoord: pixelCoord
  92. };
  93. } else {
  94. if (Math.abs(pixelCoord - lastDisplayed.pixelCoord) >= inverseNoTicks) {
  95. lastDisplayed = {
  96. pixelCoord: pixelCoord,
  97. idealPixelCoord: lastDisplayed.idealPixelCoord - inverseNoTicks
  98. };
  99. } else {
  100. tick = null;
  101. }
  102. }
  103. if (tick) {
  104. ticks.push(tick);
  105. }
  106. }
  107. // Since we went in backwards order.
  108. ticks.reverse();
  109. } else {
  110. var tickSize = plot.computeTickSize(min, max, noTicks),
  111. customAxis = {min: min, max: max, tickSize: tickSize};
  112. ticks = $.plot.linearTickGenerator(customAxis);
  113. }
  114. return ticks;
  115. };
  116. var clampAxis = function (axis, plot) {
  117. var min = axis.min,
  118. max = axis.max;
  119. if (min <= 0) {
  120. //for empty graph if axis.min is not strictly positive make it 0.1
  121. if (axis.datamin === null) {
  122. min = axis.min = 0.1;
  123. } else {
  124. min = processAxisOffset(plot, axis);
  125. }
  126. if (max < min) {
  127. axis.max = axis.datamax !== null ? axis.datamax : axis.options.max;
  128. axis.options.offset.below = 0;
  129. axis.options.offset.above = 0;
  130. }
  131. }
  132. return min;
  133. }
  134. /**
  135. - logTickFormatter(value, axis, precision)
  136. This is the corresponding tickFormatter of the logaxis.
  137. For a number greater that 10^6 or smaller than 10^(-3), this will be drawn
  138. with e representation
  139. */
  140. var logTickFormatter = function (value, axis, precision) {
  141. var tenExponent = value > 0 ? Math.floor(Math.log(value) / Math.LN10) : 0;
  142. if (precision) {
  143. if ((tenExponent >= -4) && (tenExponent <= 7)) {
  144. return $.plot.defaultTickFormatter(value, axis, precision);
  145. } else {
  146. return $.plot.expRepTickFormatter(value, axis, precision);
  147. }
  148. }
  149. if ((tenExponent >= -4) && (tenExponent <= 7)) {
  150. //if we have float numbers, return a limited length string(ex: 0.0009 is represented as 0.000900001)
  151. var formattedValue = tenExponent < 0 ? value.toFixed(-tenExponent) : value.toFixed(tenExponent + 2);
  152. if (formattedValue.indexOf('.') !== -1) {
  153. var lastZero = formattedValue.lastIndexOf('0');
  154. while (lastZero === formattedValue.length - 1) {
  155. formattedValue = formattedValue.slice(0, -1);
  156. lastZero = formattedValue.lastIndexOf('0');
  157. }
  158. //delete the dot if is last
  159. if (formattedValue.indexOf('.') === formattedValue.length - 1) {
  160. formattedValue = formattedValue.slice(0, -1);
  161. }
  162. }
  163. return formattedValue;
  164. } else {
  165. return $.plot.expRepTickFormatter(value, axis);
  166. }
  167. };
  168. /*logaxis caracteristic functions*/
  169. var logTransform = function (v) {
  170. if (v < PREFERRED_LOG_TICK_VALUES[0]) {
  171. v = PREFERRED_LOG_TICK_VALUES[0];
  172. }
  173. return Math.log(v);
  174. };
  175. var logInverseTransform = function (v) {
  176. return Math.exp(v);
  177. };
  178. var invertedTransform = function (v) {
  179. return -v;
  180. }
  181. var invertedLogTransform = function (v) {
  182. return -logTransform(v);
  183. }
  184. var invertedLogInverseTransform = function (v) {
  185. return logInverseTransform(-v);
  186. }
  187. /**
  188. - setDataminRange(plot, axis)
  189. It is used for clamping the starting point of a logarithmic axis.
  190. This will set the axis datamin range to 0.1 or to the first datapoint greater then 0.
  191. The function is usefull since the logarithmic representation can not show
  192. values less than or equal to 0.
  193. */
  194. function setDataminRange(plot, axis) {
  195. if (axis.options.mode === 'log' && axis.datamin <= 0) {
  196. if (axis.datamin === null) {
  197. axis.datamin = 0.1;
  198. } else {
  199. axis.datamin = processAxisOffset(plot, axis);
  200. }
  201. }
  202. }
  203. function processAxisOffset(plot, axis) {
  204. var series = plot.getData(),
  205. range = series
  206. .filter(function(series) {
  207. return series.xaxis === axis || series.yaxis === axis;
  208. })
  209. .map(function(series) {
  210. return plot.computeRangeForDataSeries(series, null, isValid);
  211. }),
  212. min = axis.direction === 'x'
  213. ? Math.min(0.1, range && range[0] ? range[0].xmin : 0.1)
  214. : Math.min(0.1, range && range[0] ? range[0].ymin : 0.1);
  215. axis.min = min;
  216. return min;
  217. }
  218. function isValid(a) {
  219. return a > 0;
  220. }
  221. function init(plot) {
  222. plot.hooks.processOptions.push(function (plot) {
  223. $.each(plot.getAxes(), function (axisName, axis) {
  224. var opts = axis.options;
  225. if (opts.mode === 'log') {
  226. axis.tickGenerator = function (axis) {
  227. var noTicks = 11;
  228. return logTickGenerator(plot, axis, noTicks);
  229. };
  230. if (typeof axis.options.tickFormatter !== 'function') {
  231. axis.options.tickFormatter = logTickFormatter;
  232. }
  233. axis.options.transform = opts.inverted ? invertedLogTransform : logTransform;
  234. axis.options.inverseTransform = opts.inverted ? invertedLogInverseTransform : logInverseTransform;
  235. axis.options.autoScaleMargin = 0;
  236. plot.hooks.setRange.push(setDataminRange);
  237. } else if (opts.inverted) {
  238. axis.options.transform = invertedTransform;
  239. axis.options.inverseTransform = invertedTransform;
  240. }
  241. });
  242. });
  243. }
  244. $.plot.plugins.push({
  245. init: init,
  246. options: options,
  247. name: 'log',
  248. version: '0.1'
  249. });
  250. $.plot.logTicksGenerator = logTickGenerator;
  251. $.plot.logTickFormatter = logTickFormatter;
  252. })(jQuery);