ticks.ts 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. /**
  2. * Calculate tick step.
  3. * Implementation from d3-array (ticks.js)
  4. * https://github.com/d3/d3-array/blob/master/src/ticks.js
  5. * @param start Start value
  6. * @param stop End value
  7. * @param count Ticks count
  8. */
  9. export function tickStep(start: number, stop: number, count: number): number {
  10. const e10 = Math.sqrt(50),
  11. e5 = Math.sqrt(10),
  12. e2 = Math.sqrt(2);
  13. const step0 = Math.abs(stop - start) / Math.max(0, count);
  14. let step1 = Math.pow(10, Math.floor(Math.log(step0) / Math.LN10));
  15. const error = step0 / step1;
  16. if (error >= e10) {
  17. step1 *= 10;
  18. } else if (error >= e5) {
  19. step1 *= 5;
  20. } else if (error >= e2) {
  21. step1 *= 2;
  22. }
  23. return stop < start ? -step1 : step1;
  24. }
  25. export function getScaledDecimals(decimals: number, tickSize: number) {
  26. return decimals - Math.floor(Math.log(tickSize) / Math.LN10);
  27. }
  28. /**
  29. * Calculate tick size based on min and max values, number of ticks and precision.
  30. * Implementation from Flot.
  31. * @param min Axis minimum
  32. * @param max Axis maximum
  33. * @param noTicks Number of ticks
  34. * @param tickDecimals Tick decimal precision
  35. */
  36. export function getFlotTickSize(min: number, max: number, noTicks: number, tickDecimals: number) {
  37. const delta = (max - min) / noTicks;
  38. let dec = -Math.floor(Math.log(delta) / Math.LN10);
  39. const maxDec = tickDecimals;
  40. const magn = Math.pow(10, -dec);
  41. const norm = delta / magn; // norm is between 1.0 and 10.0
  42. let size;
  43. if (norm < 1.5) {
  44. size = 1;
  45. } else if (norm < 3) {
  46. size = 2;
  47. // special case for 2.5, requires an extra decimal
  48. if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) {
  49. size = 2.5;
  50. ++dec;
  51. }
  52. } else if (norm < 7.5) {
  53. size = 5;
  54. } else {
  55. size = 10;
  56. }
  57. size *= magn;
  58. return size;
  59. }
  60. /**
  61. * Calculate axis range (min and max).
  62. * Implementation from Flot.
  63. */
  64. export function getFlotRange(panelMin: any, panelMax: any, datamin: number, datamax: number) {
  65. const autoscaleMargin = 0.02;
  66. let min = +(panelMin != null ? panelMin : datamin);
  67. let max = +(panelMax != null ? panelMax : datamax);
  68. const delta = max - min;
  69. if (delta === 0.0) {
  70. // Grafana fix: wide Y min and max using increased wideFactor
  71. // when all series values are the same
  72. const wideFactor = 0.25;
  73. const widen = Math.abs(max === 0 ? 1 : max * wideFactor);
  74. if (panelMin === null) {
  75. min -= widen;
  76. }
  77. // always widen max if we couldn't widen min to ensure we
  78. // don't fall into min == max which doesn't work
  79. if (panelMax == null || panelMin != null) {
  80. max += widen;
  81. }
  82. } else {
  83. // consider autoscaling
  84. const margin = autoscaleMargin;
  85. if (margin != null) {
  86. if (panelMin == null) {
  87. min -= delta * margin;
  88. // make sure we don't go below zero if all values
  89. // are positive
  90. if (min < 0 && datamin != null && datamin >= 0) {
  91. min = 0;
  92. }
  93. }
  94. if (panelMax == null) {
  95. max += delta * margin;
  96. if (max > 0 && datamax != null && datamax <= 0) {
  97. max = 0;
  98. }
  99. }
  100. }
  101. }
  102. return { min, max };
  103. }
  104. /**
  105. * Calculate tick decimals.
  106. * Implementation from Flot.
  107. */
  108. export function getFlotTickDecimals(datamin: number, datamax: number, axis: { min: any; max: any }, height: number) {
  109. const { min, max } = getFlotRange(axis.min, axis.max, datamin, datamax);
  110. const noTicks = 0.3 * Math.sqrt(height);
  111. const delta = (max - min) / noTicks;
  112. const dec = -Math.floor(Math.log(delta) / Math.LN10);
  113. const magn = Math.pow(10, -dec);
  114. // norm is between 1.0 and 10.0
  115. const norm = delta / magn;
  116. let size;
  117. if (norm < 1.5) {
  118. size = 1;
  119. } else if (norm < 3) {
  120. size = 2;
  121. // special case for 2.5, requires an extra decimal
  122. if (norm > 2.25) {
  123. size = 2.5;
  124. }
  125. } else if (norm < 7.5) {
  126. size = 5;
  127. } else {
  128. size = 10;
  129. }
  130. size *= magn;
  131. const tickDecimals = Math.max(0, -Math.floor(Math.log(delta) / Math.LN10) + 1);
  132. // grafana addition
  133. const scaledDecimals = tickDecimals - Math.floor(Math.log(size) / Math.LN10);
  134. return { tickDecimals, scaledDecimals };
  135. }
  136. /**
  137. * Format timestamp similar to Grafana graph panel.
  138. * @param ticks Number of ticks
  139. * @param min Time from (in milliseconds)
  140. * @param max Time to (in milliseconds)
  141. */
  142. export function grafanaTimeFormat(ticks: number, min: number, max: number) {
  143. if (min && max && ticks) {
  144. const range = max - min;
  145. const secPerTick = range / ticks / 1000;
  146. const oneDay = 86400000;
  147. const oneYear = 31536000000;
  148. if (secPerTick <= 45) {
  149. return 'HH:mm:ss';
  150. }
  151. if (secPerTick <= 7200 || range <= oneDay) {
  152. return 'HH:mm';
  153. }
  154. if (secPerTick <= 80000) {
  155. return 'MM/DD HH:mm';
  156. }
  157. if (secPerTick <= 2419200 || range <= oneYear) {
  158. return 'MM/DD';
  159. }
  160. return 'YYYY-MM';
  161. }
  162. return 'HH:mm';
  163. }
  164. /**
  165. * Logarithm of value for arbitrary base.
  166. */
  167. export function logp(value: number, base: number) {
  168. return Math.log(value) / Math.log(base);
  169. }
  170. /**
  171. * Get decimal precision of number (3.14 => 2)
  172. */
  173. export function getPrecision(num: number): number {
  174. const str = num.toString();
  175. return getStringPrecision(str);
  176. }
  177. /**
  178. * Get decimal precision of number stored as a string ("3.14" => 2)
  179. */
  180. export function getStringPrecision(num: string): number {
  181. if (isNaN(num as unknown as number)) {
  182. return 0;
  183. }
  184. const dotIndex = num.indexOf('.');
  185. if (dotIndex === -1) {
  186. return 0;
  187. } else {
  188. return num.length - dotIndex - 1;
  189. }
  190. }