mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
heatmap: refactor
This commit is contained in:
parent
a791a92d79
commit
18a90667ba
@ -156,3 +156,61 @@ export function getFlotTickDecimals(data, axis) {
|
|||||||
const scaledDecimals = tickDecimals - Math.floor(Math.log(size) / Math.LN10);
|
const scaledDecimals = tickDecimals - Math.floor(Math.log(size) / Math.LN10);
|
||||||
return { tickDecimals, scaledDecimals };
|
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, min, max) {
|
||||||
|
if (min && max && ticks) {
|
||||||
|
let range = max - min;
|
||||||
|
let secPerTick = range / ticks / 1000;
|
||||||
|
let oneDay = 86400000;
|
||||||
|
let oneYear = 31536000000;
|
||||||
|
|
||||||
|
if (secPerTick <= 45) {
|
||||||
|
return '%H:%M:%S';
|
||||||
|
}
|
||||||
|
if (secPerTick <= 7200 || range <= oneDay) {
|
||||||
|
return '%H:%M';
|
||||||
|
}
|
||||||
|
if (secPerTick <= 80000) {
|
||||||
|
return '%m/%d %H:%M';
|
||||||
|
}
|
||||||
|
if (secPerTick <= 2419200 || range <= oneYear) {
|
||||||
|
return '%m/%d';
|
||||||
|
}
|
||||||
|
return '%Y-%m';
|
||||||
|
}
|
||||||
|
|
||||||
|
return '%H:%M';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logarithm of value for arbitrary base.
|
||||||
|
*/
|
||||||
|
export function logp(value, base) {
|
||||||
|
return Math.log(value) / Math.log(base);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get decimal precision of number (3.14 => 2)
|
||||||
|
*/
|
||||||
|
export function getPrecision(num: number): number {
|
||||||
|
let str = num.toString();
|
||||||
|
return getStringPrecision(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get decimal precision of number stored as a string ("3.14" => 2)
|
||||||
|
*/
|
||||||
|
export function getStringPrecision(num: string): number {
|
||||||
|
let dot_index = num.indexOf('.');
|
||||||
|
if (dot_index === -1) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return num.length - dot_index - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -89,6 +89,8 @@ let colorSchemes = [
|
|||||||
{ name: 'YlOrRd', value: 'interpolateYlOrRd', invert: 'darm' },
|
{ name: 'YlOrRd', value: 'interpolateYlOrRd', invert: 'darm' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const ds_support_histogram_sort = ['prometheus', 'elasticsearch'];
|
||||||
|
|
||||||
export class HeatmapCtrl extends MetricsPanelCtrl {
|
export class HeatmapCtrl extends MetricsPanelCtrl {
|
||||||
static templateUrl = 'module.html';
|
static templateUrl = 'module.html';
|
||||||
|
|
||||||
@ -207,15 +209,20 @@ export class HeatmapCtrl extends MetricsPanelCtrl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
convertHistogramToHeatmapData() {
|
convertHistogramToHeatmapData() {
|
||||||
|
const panelDatasource = this.getPanelDataSourceType();
|
||||||
let xBucketSize, yBucketSize, bucketsData, tsBuckets;
|
let xBucketSize, yBucketSize, bucketsData, tsBuckets;
|
||||||
|
|
||||||
|
// Try to sort series by bucket bound, if datasource doesn't do it.
|
||||||
|
if (!_.includes(ds_support_histogram_sort, panelDatasource)) {
|
||||||
|
this.series.sort(sortSeriesByLabel);
|
||||||
|
}
|
||||||
|
|
||||||
// Convert histogram to heatmap. Each histogram bucket represented by the series which name is
|
// Convert histogram to heatmap. Each histogram bucket represented by the series which name is
|
||||||
// a top (or bottom, depends of datasource) bucket bound. Further, these values will be used as X axis labels.
|
// a top (or bottom, depends of datasource) bucket bound. Further, these values will be used as X axis labels.
|
||||||
this.series.sort(sortSeriesByLabel);
|
|
||||||
bucketsData = histogramToHeatmap(this.series);
|
bucketsData = histogramToHeatmap(this.series);
|
||||||
|
|
||||||
tsBuckets = _.map(this.series, 'label');
|
tsBuckets = _.map(this.series, 'label');
|
||||||
if (this.datasource && this.datasource.type === 'prometheus') {
|
if (panelDatasource === 'prometheus') {
|
||||||
// Prometheus labels are upper inclusive bounds, so add empty bottom bucket label.
|
// Prometheus labels are upper inclusive bounds, so add empty bottom bucket label.
|
||||||
tsBuckets = [''].concat(tsBuckets);
|
tsBuckets = [''].concat(tsBuckets);
|
||||||
} else {
|
} else {
|
||||||
@ -241,6 +248,14 @@ export class HeatmapCtrl extends MetricsPanelCtrl {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getPanelDataSourceType() {
|
||||||
|
if (this.datasource.meta && this.datasource.meta.id) {
|
||||||
|
return this.datasource.meta.id;
|
||||||
|
} else {
|
||||||
|
return 'unknown';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onDataReceived(dataList) {
|
onDataReceived(dataList) {
|
||||||
this.series = dataList.map(this.seriesHandler.bind(this));
|
this.series = dataList.map(this.seriesHandler.bind(this));
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ function sortSeriesByLabel(s1, s2) {
|
|||||||
label1 = parseHistogramLabel(s1.label);
|
label1 = parseHistogramLabel(s1.label);
|
||||||
label2 = parseHistogramLabel(s2.label);
|
label2 = parseHistogramLabel(s2.label);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err);
|
console.log(err.message || err);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,10 +83,14 @@ function sortSeriesByLabel(s1, s2) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function parseHistogramLabel(label: string): number {
|
function parseHistogramLabel(label: string): number {
|
||||||
if (label === '+Inf') {
|
if (label === '+Inf' || label === 'inf') {
|
||||||
return +Infinity;
|
return +Infinity;
|
||||||
}
|
}
|
||||||
return Number(label);
|
const value = Number(label);
|
||||||
|
if (isNaN(value)) {
|
||||||
|
throw new Error(`Error parsing histogram label: ${label} is not a number`);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -100,14 +100,14 @@ export class HeatmapTooltip {
|
|||||||
let countValueFormatter, bucketBoundFormatter;
|
let countValueFormatter, bucketBoundFormatter;
|
||||||
if (_.isNumber(this.panel.tooltipDecimals)) {
|
if (_.isNumber(this.panel.tooltipDecimals)) {
|
||||||
countValueFormatter = this.countValueFormatter(this.panel.tooltipDecimals, null);
|
countValueFormatter = this.countValueFormatter(this.panel.tooltipDecimals, null);
|
||||||
bucketBoundFormatter = this.bucketBoundFormatter(this.panel.tooltipDecimals, null);
|
bucketBoundFormatter = this.panelCtrl.tickValueFormatter(this.panelCtrl.decimals, null);
|
||||||
} else {
|
} else {
|
||||||
// auto decimals
|
// auto decimals
|
||||||
// legend and tooltip gets one more decimal precision
|
// legend and tooltip gets one more decimal precision
|
||||||
// than graph legend ticks
|
// than graph legend ticks
|
||||||
let decimals = (this.panelCtrl.decimals || -1) + 1;
|
let decimals = (this.panelCtrl.decimals || -1) + 1;
|
||||||
countValueFormatter = this.countValueFormatter(decimals, this.panelCtrl.scaledDecimals + 2);
|
countValueFormatter = this.countValueFormatter(decimals, this.panelCtrl.scaledDecimals + 2);
|
||||||
bucketBoundFormatter = this.bucketBoundFormatter(decimals, this.panelCtrl.scaledDecimals + 2);
|
bucketBoundFormatter = this.panelCtrl.tickValueFormatter(decimals, this.panelCtrl.scaledDecimals + 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
let tooltipHtml = `<div class="graph-tooltip-time">${time}</div>
|
let tooltipHtml = `<div class="graph-tooltip-time">${time}</div>
|
||||||
@ -116,19 +116,13 @@ export class HeatmapTooltip {
|
|||||||
if (yData) {
|
if (yData) {
|
||||||
if (yData.bounds) {
|
if (yData.bounds) {
|
||||||
if (data.tsBuckets) {
|
if (data.tsBuckets) {
|
||||||
const decimals = this.panelCtrl.decimals || 0;
|
// Use Y-axis labels
|
||||||
const tickFormatter = valIndex => {
|
const tickFormatter = valIndex => {
|
||||||
let valueFormatted = data.tsBuckets[valIndex];
|
return data.tsBucketsFormatted ? data.tsBucketsFormatted[valIndex] : data.tsBuckets[valIndex];
|
||||||
if (!_.isNaN(_.toNumber(valueFormatted)) && valueFormatted !== '') {
|
|
||||||
// Try to format numeric tick labels
|
|
||||||
valueFormatted = this.bucketBoundFormatter(decimals)(_.toNumber(valueFormatted));
|
|
||||||
}
|
|
||||||
return valueFormatted;
|
|
||||||
};
|
};
|
||||||
const tsBucketsTickFormatter = tickFormatter.bind(this);
|
|
||||||
|
|
||||||
boundBottom = tsBucketsTickFormatter(yBucketIndex);
|
boundBottom = tickFormatter(yBucketIndex);
|
||||||
boundTop = yBucketIndex < data.tsBuckets.length - 1 ? tsBucketsTickFormatter(yBucketIndex + 1) : '';
|
boundTop = yBucketIndex < data.tsBuckets.length - 1 ? tickFormatter(yBucketIndex + 1) : '';
|
||||||
} else {
|
} else {
|
||||||
// Display 0 if bucket is a special 'zero' bucket
|
// Display 0 if bucket is a special 'zero' bucket
|
||||||
let bottom = yData.y ? yData.bounds.bottom : 0;
|
let bottom = yData.y ? yData.bounds.bottom : 0;
|
||||||
@ -282,21 +276,9 @@ export class HeatmapTooltip {
|
|||||||
}
|
}
|
||||||
|
|
||||||
countValueFormatter(decimals, scaledDecimals = null) {
|
countValueFormatter(decimals, scaledDecimals = null) {
|
||||||
let format = 'none';
|
let format = 'short';
|
||||||
return function(value) {
|
return function(value) {
|
||||||
return kbn.valueFormats[format](value, decimals, scaledDecimals);
|
return kbn.valueFormats[format](value, decimals, scaledDecimals);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
bucketBoundFormatter(decimals, scaledDecimals = null) {
|
|
||||||
let format = this.panel.yAxis.format;
|
|
||||||
return function(value) {
|
|
||||||
try {
|
|
||||||
return format !== 'none' ? kbn.valueFormats[format](value, decimals, scaledDecimals) : value;
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err.message || err);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import moment from 'moment';
|
|||||||
import * as d3 from 'd3';
|
import * as d3 from 'd3';
|
||||||
import kbn from 'app/core/utils/kbn';
|
import kbn from 'app/core/utils/kbn';
|
||||||
import { appEvents, contextSrv } from 'app/core/core';
|
import { appEvents, contextSrv } from 'app/core/core';
|
||||||
import { tickStep, getScaledDecimals, getFlotTickSize } from 'app/core/utils/ticks';
|
import * as ticksUtils from 'app/core/utils/ticks';
|
||||||
import { HeatmapTooltip } from './heatmap_tooltip';
|
import { HeatmapTooltip } from './heatmap_tooltip';
|
||||||
import { mergeZeroBuckets } from './heatmap_data_converter';
|
import { mergeZeroBuckets } from './heatmap_data_converter';
|
||||||
import { getColorScale, getOpacityScale } from './color_scale';
|
import { getColorScale, getOpacityScale } from './color_scale';
|
||||||
@ -108,7 +108,7 @@ export default function link(scope, elem, attrs, ctrl) {
|
|||||||
.range([0, chartWidth]);
|
.range([0, chartWidth]);
|
||||||
|
|
||||||
let ticks = chartWidth / DEFAULT_X_TICK_SIZE_PX;
|
let ticks = chartWidth / DEFAULT_X_TICK_SIZE_PX;
|
||||||
let grafanaTimeFormatter = grafanaTimeFormat(ticks, timeRange.from, timeRange.to);
|
let grafanaTimeFormatter = ticksUtils.grafanaTimeFormat(ticks, timeRange.from, timeRange.to);
|
||||||
let timeFormat;
|
let timeFormat;
|
||||||
let dashboardTimeZone = ctrl.dashboard.getTimezone();
|
let dashboardTimeZone = ctrl.dashboard.getTimezone();
|
||||||
if (dashboardTimeZone === 'utc') {
|
if (dashboardTimeZone === 'utc') {
|
||||||
@ -141,7 +141,7 @@ export default function link(scope, elem, attrs, ctrl) {
|
|||||||
|
|
||||||
function addYAxis() {
|
function addYAxis() {
|
||||||
let ticks = Math.ceil(chartHeight / DEFAULT_Y_TICK_SIZE_PX);
|
let ticks = Math.ceil(chartHeight / DEFAULT_Y_TICK_SIZE_PX);
|
||||||
let tick_interval = tickStep(data.heatmapStats.min, data.heatmapStats.max, ticks);
|
let tick_interval = ticksUtils.tickStep(data.heatmapStats.min, data.heatmapStats.max, ticks);
|
||||||
let { y_min, y_max } = wideYAxisRange(data.heatmapStats.min, data.heatmapStats.max, tick_interval);
|
let { y_min, y_max } = wideYAxisRange(data.heatmapStats.min, data.heatmapStats.max, tick_interval);
|
||||||
|
|
||||||
// Rewrite min and max if it have been set explicitly
|
// Rewrite min and max if it have been set explicitly
|
||||||
@ -149,14 +149,14 @@ export default function link(scope, elem, attrs, ctrl) {
|
|||||||
y_max = panel.yAxis.max !== null ? panel.yAxis.max : y_max;
|
y_max = panel.yAxis.max !== null ? panel.yAxis.max : y_max;
|
||||||
|
|
||||||
// Adjust ticks after Y range widening
|
// Adjust ticks after Y range widening
|
||||||
tick_interval = tickStep(y_min, y_max, ticks);
|
tick_interval = ticksUtils.tickStep(y_min, y_max, ticks);
|
||||||
ticks = Math.ceil((y_max - y_min) / tick_interval);
|
ticks = Math.ceil((y_max - y_min) / tick_interval);
|
||||||
|
|
||||||
let decimalsAuto = getPrecision(tick_interval);
|
let decimalsAuto = ticksUtils.getPrecision(tick_interval);
|
||||||
let decimals = panel.yAxis.decimals === null ? decimalsAuto : panel.yAxis.decimals;
|
let decimals = panel.yAxis.decimals === null ? decimalsAuto : panel.yAxis.decimals;
|
||||||
// Calculate scaledDecimals for log scales using tick size (as in jquery.flot.js)
|
// Calculate scaledDecimals for log scales using tick size (as in jquery.flot.js)
|
||||||
let flot_tick_size = getFlotTickSize(y_min, y_max, ticks, decimalsAuto);
|
let flot_tick_size = ticksUtils.getFlotTickSize(y_min, y_max, ticks, decimalsAuto);
|
||||||
let scaledDecimals = getScaledDecimals(decimals, flot_tick_size);
|
let scaledDecimals = ticksUtils.getScaledDecimals(decimals, flot_tick_size);
|
||||||
ctrl.decimals = decimals;
|
ctrl.decimals = decimals;
|
||||||
ctrl.scaledDecimals = scaledDecimals;
|
ctrl.scaledDecimals = scaledDecimals;
|
||||||
|
|
||||||
@ -248,12 +248,12 @@ export default function link(scope, elem, attrs, ctrl) {
|
|||||||
let domain = yScale.domain();
|
let domain = yScale.domain();
|
||||||
let tick_values = logScaleTickValues(domain, log_base);
|
let tick_values = logScaleTickValues(domain, log_base);
|
||||||
|
|
||||||
let decimalsAuto = getPrecision(y_min);
|
let decimalsAuto = ticksUtils.getPrecision(y_min);
|
||||||
let decimals = panel.yAxis.decimals || decimalsAuto;
|
let decimals = panel.yAxis.decimals || decimalsAuto;
|
||||||
|
|
||||||
// Calculate scaledDecimals for log scales using tick size (as in jquery.flot.js)
|
// Calculate scaledDecimals for log scales using tick size (as in jquery.flot.js)
|
||||||
let flot_tick_size = getFlotTickSize(y_min, y_max, tick_values.length, decimalsAuto);
|
let flot_tick_size = ticksUtils.getFlotTickSize(y_min, y_max, tick_values.length, decimalsAuto);
|
||||||
let scaledDecimals = getScaledDecimals(decimals, flot_tick_size);
|
let scaledDecimals = ticksUtils.getScaledDecimals(decimals, flot_tick_size);
|
||||||
ctrl.decimals = decimals;
|
ctrl.decimals = decimals;
|
||||||
ctrl.scaledDecimals = scaledDecimals;
|
ctrl.scaledDecimals = scaledDecimals;
|
||||||
|
|
||||||
@ -305,7 +305,7 @@ export default function link(scope, elem, attrs, ctrl) {
|
|||||||
.range([chartHeight, 0]);
|
.range([chartHeight, 0]);
|
||||||
|
|
||||||
const tick_values = _.map(tsBuckets, (b, i) => i);
|
const tick_values = _.map(tsBuckets, (b, i) => i);
|
||||||
const decimalsAuto = _.max(_.map(tsBuckets, getStringPrecision));
|
const decimalsAuto = _.max(_.map(tsBuckets, ticksUtils.getStringPrecision));
|
||||||
const decimals = panel.yAxis.decimals === null ? decimalsAuto : panel.yAxis.decimals;
|
const decimals = panel.yAxis.decimals === null ? decimalsAuto : panel.yAxis.decimals;
|
||||||
ctrl.decimals = decimals;
|
ctrl.decimals = decimals;
|
||||||
|
|
||||||
@ -318,6 +318,9 @@ export default function link(scope, elem, attrs, ctrl) {
|
|||||||
return valueFormatted;
|
return valueFormatted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tsBucketsFormatted = _.map(tsBuckets, (v, i) => tickFormatter(i));
|
||||||
|
data.tsBucketsFormatted = tsBucketsFormatted;
|
||||||
|
|
||||||
let yAxis = d3
|
let yAxis = d3
|
||||||
.axisLeft(yScale)
|
.axisLeft(yScale)
|
||||||
.tickValues(tick_values)
|
.tickValues(tick_values)
|
||||||
@ -361,11 +364,11 @@ export default function link(scope, elem, attrs, ctrl) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function adjustLogMax(max, base) {
|
function adjustLogMax(max, base) {
|
||||||
return Math.pow(base, Math.ceil(logp(max, base)));
|
return Math.pow(base, Math.ceil(ticksUtils.logp(max, base)));
|
||||||
}
|
}
|
||||||
|
|
||||||
function adjustLogMin(min, base) {
|
function adjustLogMin(min, base) {
|
||||||
return Math.pow(base, Math.floor(logp(min, base)));
|
return Math.pow(base, Math.floor(ticksUtils.logp(min, base)));
|
||||||
}
|
}
|
||||||
|
|
||||||
function logScaleTickValues(domain, base) {
|
function logScaleTickValues(domain, base) {
|
||||||
@ -374,14 +377,14 @@ export default function link(scope, elem, attrs, ctrl) {
|
|||||||
let tickValues = [];
|
let tickValues = [];
|
||||||
|
|
||||||
if (domainMin < 1) {
|
if (domainMin < 1) {
|
||||||
let under_one_ticks = Math.floor(logp(domainMin, base));
|
let under_one_ticks = Math.floor(ticksUtils.logp(domainMin, base));
|
||||||
for (let i = under_one_ticks; i < 0; i++) {
|
for (let i = under_one_ticks; i < 0; i++) {
|
||||||
let tick_value = Math.pow(base, i);
|
let tick_value = Math.pow(base, i);
|
||||||
tickValues.push(tick_value);
|
tickValues.push(tick_value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let ticks = Math.ceil(logp(domainMax, base));
|
let ticks = Math.ceil(ticksUtils.logp(domainMax, base));
|
||||||
for (let i = 0; i <= ticks; i++) {
|
for (let i = 0; i <= ticks; i++) {
|
||||||
let tick_value = Math.pow(base, i);
|
let tick_value = Math.pow(base, i);
|
||||||
tickValues.push(tick_value);
|
tickValues.push(tick_value);
|
||||||
@ -402,6 +405,8 @@ export default function link(scope, elem, attrs, ctrl) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctrl.tickValueFormatter = tickValueFormatter;
|
||||||
|
|
||||||
function fixYAxisTickSize() {
|
function fixYAxisTickSize() {
|
||||||
heatmap
|
heatmap
|
||||||
.select('.axis-y')
|
.select('.axis-y')
|
||||||
@ -827,46 +832,3 @@ export default function link(scope, elem, attrs, ctrl) {
|
|||||||
$heatmap.on('mousemove', onMouseMove);
|
$heatmap.on('mousemove', onMouseMove);
|
||||||
$heatmap.on('mouseleave', onMouseLeave);
|
$heatmap.on('mouseleave', onMouseLeave);
|
||||||
}
|
}
|
||||||
|
|
||||||
function grafanaTimeFormat(ticks, min, max) {
|
|
||||||
if (min && max && ticks) {
|
|
||||||
let range = max - min;
|
|
||||||
let secPerTick = range / ticks / 1000;
|
|
||||||
let oneDay = 86400000;
|
|
||||||
let oneYear = 31536000000;
|
|
||||||
|
|
||||||
if (secPerTick <= 45) {
|
|
||||||
return '%H:%M:%S';
|
|
||||||
}
|
|
||||||
if (secPerTick <= 7200 || range <= oneDay) {
|
|
||||||
return '%H:%M';
|
|
||||||
}
|
|
||||||
if (secPerTick <= 80000) {
|
|
||||||
return '%m/%d %H:%M';
|
|
||||||
}
|
|
||||||
if (secPerTick <= 2419200 || range <= oneYear) {
|
|
||||||
return '%m/%d';
|
|
||||||
}
|
|
||||||
return '%Y-%m';
|
|
||||||
}
|
|
||||||
|
|
||||||
return '%H:%M';
|
|
||||||
}
|
|
||||||
|
|
||||||
function logp(value, base) {
|
|
||||||
return Math.log(value) / Math.log(base);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPrecision(num: number): number {
|
|
||||||
let str = num.toString();
|
|
||||||
return getStringPrecision(str);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getStringPrecision(num: string): number {
|
|
||||||
let dot_index = num.indexOf('.');
|
|
||||||
if (dot_index === -1) {
|
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
return num.length - dot_index - 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user