/** * Calculate tick step. * Implementation from d3-array (ticks.js) * https://github.com/d3/d3-array/blob/master/src/ticks.js * @param start Start value * @param stop End value * @param count Ticks count */ export function tickStep(start: number, stop: number, count: number): number { const e10 = Math.sqrt(50), e5 = Math.sqrt(10), e2 = Math.sqrt(2); const step0 = Math.abs(stop - start) / Math.max(0, count); let step1 = Math.pow(10, Math.floor(Math.log(step0) / Math.LN10)); const error = step0 / step1; if (error >= e10) { step1 *= 10; } else if (error >= e5) { step1 *= 5; } else if (error >= e2) { step1 *= 2; } return stop < start ? -step1 : step1; } export function getScaledDecimals(decimals: number, tickSize: number) { return decimals - Math.floor(Math.log(tickSize) / Math.LN10); } /** * Calculate tick size based on min and max values, number of ticks and precision. * Implementation from Flot. * @param min Axis minimum * @param max Axis maximum * @param noTicks Number of ticks * @param tickDecimals Tick decimal precision */ export function getFlotTickSize(min: number, max: number, noTicks: number, tickDecimals: number) { const delta = (max - min) / noTicks; let dec = -Math.floor(Math.log(delta) / Math.LN10); const maxDec = tickDecimals; const magn = Math.pow(10, -dec); const norm = delta / magn; // norm is between 1.0 and 10.0 let size; if (norm < 1.5) { size = 1; } else if (norm < 3) { size = 2; // special case for 2.5, requires an extra decimal if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) { size = 2.5; ++dec; } } else if (norm < 7.5) { size = 5; } else { size = 10; } size *= magn; return size; } /** * Calculate axis range (min and max). * Implementation from Flot. */ export function getFlotRange(panelMin: any, panelMax: any, datamin: number, datamax: number) { const autoscaleMargin = 0.02; let min = +(panelMin != null ? panelMin : datamin); let max = +(panelMax != null ? panelMax : datamax); const delta = max - min; if (delta === 0.0) { // Grafana fix: wide Y min and max using increased wideFactor // when all series values are the same const wideFactor = 0.25; const widen = Math.abs(max === 0 ? 1 : max * wideFactor); if (panelMin === null) { min -= widen; } // always widen max if we couldn't widen min to ensure we // don't fall into min == max which doesn't work if (panelMax == null || panelMin != null) { max += widen; } } else { // consider autoscaling const margin = autoscaleMargin; if (margin != null) { if (panelMin == null) { min -= delta * margin; // make sure we don't go below zero if all values // are positive if (min < 0 && datamin != null && datamin >= 0) { min = 0; } } if (panelMax == null) { max += delta * margin; if (max > 0 && datamax != null && datamax <= 0) { max = 0; } } } } return { min, max }; } /** * Calculate tick decimals. * Implementation from Flot. */ export function getFlotTickDecimals(datamin: number, datamax: number, axis: { min: any; max: any }, height: number) { const { min, max } = getFlotRange(axis.min, axis.max, datamin, datamax); const noTicks = 0.3 * Math.sqrt(height); const delta = (max - min) / noTicks; const dec = -Math.floor(Math.log(delta) / Math.LN10); const magn = Math.pow(10, -dec); // norm is between 1.0 and 10.0 const norm = delta / magn; let size; if (norm < 1.5) { size = 1; } else if (norm < 3) { size = 2; // special case for 2.5, requires an extra decimal if (norm > 2.25) { size = 2.5; } } else if (norm < 7.5) { size = 5; } else { size = 10; } size *= magn; const tickDecimals = Math.max(0, -Math.floor(Math.log(delta) / Math.LN10) + 1); // grafana addition const scaledDecimals = tickDecimals - Math.floor(Math.log(size) / Math.LN10); return { tickDecimals, scaledDecimals }; } /** * Format timestamp similar to Grafana graph panel. * @param ticks Number of ticks * @param min Time from (in milliseconds) * @param max Time to (in milliseconds) */ export function grafanaTimeFormat(ticks: number, min: number, max: number) { if (min && max && ticks) { const range = max - min; const secPerTick = range / ticks / 1000; const oneDay = 86400000; const oneYear = 31536000000; if (secPerTick <= 45) { return 'HH:mm:ss'; } if (secPerTick <= 7200 || range <= oneDay) { return 'HH:mm'; } if (secPerTick <= 80000) { return 'MM/DD HH:mm'; } if (secPerTick <= 2419200 || range <= oneYear) { return 'MM/DD'; } return 'YYYY-MM'; } return 'HH:mm'; } /** * Logarithm of value for arbitrary base. */ export function logp(value: number, base: number) { return Math.log(value) / Math.log(base); } /** * Get decimal precision of number (3.14 => 2) */ export function getPrecision(num: number): number { const str = num.toString(); return getStringPrecision(str); } /** * Get decimal precision of number stored as a string ("3.14" => 2) */ export function getStringPrecision(num: string): number { if (isNaN(num as unknown as number)) { return 0; } const dotIndex = num.indexOf('.'); if (dotIndex === -1) { return 0; } else { return num.length - dotIndex - 1; } }