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