mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
heatmap: add function for bucket size calculation
This commit is contained in:
@@ -44,7 +44,7 @@ function convertEsSeriesToHeatmap(series: TimeSeries, saveZeroCounts = false) {
|
||||
buckets: valueBuckets
|
||||
};
|
||||
|
||||
// Don't push buckets vith 0 count until saveZeroCounts flag is set
|
||||
// Don't push buckets with 0 count until saveZeroCounts flag is set
|
||||
if (count !== 0 || (count === 0 && saveZeroCounts)) {
|
||||
xBuckets.push(xBucket);
|
||||
}
|
||||
@@ -404,11 +404,55 @@ function getMinLog(series) {
|
||||
return _.min(values);
|
||||
}
|
||||
|
||||
// Logarithm for custom base
|
||||
/**
|
||||
* Logarithm for custom base
|
||||
* @param value
|
||||
* @param base logarithm base
|
||||
*/
|
||||
function logp(value, base) {
|
||||
return Math.log(value) / Math.log(base);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate size of Y bucket from given buckets bounds.
|
||||
* @param bounds Array of Y buckets bounds
|
||||
* @param logBase Logarithm base
|
||||
*/
|
||||
function calculateBucketSize(bounds: number[], logBase = 1): number {
|
||||
let bucketSize = Infinity;
|
||||
|
||||
if (bounds.length === 0) {
|
||||
return 0;
|
||||
} else if (bounds.length === 1) {
|
||||
return bounds[0];
|
||||
} else {
|
||||
bounds = _.sortBy(bounds);
|
||||
for (let i = 1; i < bounds.length; i++) {
|
||||
let distance = getDistance(bounds[i], bounds[i - 1], logBase);
|
||||
bucketSize = distance < bucketSize ? distance : bucketSize;
|
||||
}
|
||||
}
|
||||
|
||||
return bucketSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate distance between two numbers in given scale (linear or logarithmic).
|
||||
* @param a
|
||||
* @param b
|
||||
* @param logBase
|
||||
*/
|
||||
function getDistance(a: number, b: number, logBase = 1): number {
|
||||
if (logBase === 1) {
|
||||
// Linear distance
|
||||
return Math.abs(b - a);
|
||||
} else {
|
||||
// logarithmic distance
|
||||
let ratio = Math.max(a, b) / Math.min(a, b);
|
||||
return logp(ratio, logBase);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two heatmap data objects
|
||||
* @param objA
|
||||
@@ -427,7 +471,7 @@ function isHeatmapDataEqual(objA: any, objB: any): boolean {
|
||||
_.forEach(xBucket.buckets, (yBucket: YBucket, y) => {
|
||||
if (objB[x].buckets && objB[x].buckets[y]) {
|
||||
if (objB[x].buckets[y].values) {
|
||||
is_eql = _.isEqual(yBucket.values.sort(), objB[x].buckets[y].values.sort());
|
||||
is_eql = _.isEqual(_.sortBy(yBucket.values), _.sortBy(objB[x].buckets[y].values));
|
||||
if (!is_eql) {
|
||||
return false;
|
||||
}
|
||||
@@ -465,5 +509,6 @@ export {
|
||||
mergeZeroBuckets,
|
||||
getMinLog,
|
||||
getValueBucketBound,
|
||||
isHeatmapDataEqual
|
||||
isHeatmapDataEqual,
|
||||
calculateBucketSize
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import _ from 'lodash';
|
||||
import { describe, beforeEach, it, sinon, expect, angularMocks } from '../../../../../test/lib/common';
|
||||
import TimeSeries from 'app/core/time_series2';
|
||||
import { convertToHeatMap, elasticHistogramToHeatmap, isHeatmapDataEqual } from '../heatmap_data_converter';
|
||||
import { convertToHeatMap, elasticHistogramToHeatmap, calculateBucketSize, isHeatmapDataEqual } from '../heatmap_data_converter';
|
||||
|
||||
describe('isHeatmapDataEqual', () => {
|
||||
let ctx: any = {};
|
||||
@@ -64,6 +64,55 @@ describe('isHeatmapDataEqual', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('calculateBucketSize', () => {
|
||||
let ctx: any = {};
|
||||
|
||||
describe('when logBase is 1 (linear scale)', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
ctx.logBase = 1;
|
||||
ctx.bounds_set = [
|
||||
{ bounds: [], size: 0 },
|
||||
{ bounds: [0], size: 0 },
|
||||
{ bounds: [4], size: 4 },
|
||||
{ bounds: [0, 1, 2, 3, 4], size: 1 },
|
||||
{ bounds: [0, 1, 3, 5, 7], size: 1 },
|
||||
{ bounds: [0, 3, 7, 9, 15], size: 2 },
|
||||
{ bounds: [0, 7, 3, 15, 9], size: 2 },
|
||||
{ bounds: [0, 5, 10, 15, 50], size: 5 }
|
||||
];
|
||||
});
|
||||
|
||||
it('should properly calculate bucket size', () => {
|
||||
_.each(ctx.bounds_set, (b) => {
|
||||
let bucketSize = calculateBucketSize(b.bounds, ctx.logBase);
|
||||
expect(bucketSize).to.be(b.size);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when logBase is 2', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
ctx.logBase = 2;
|
||||
ctx.bounds_set = [
|
||||
{ bounds: [], size: 0 },
|
||||
{ bounds: [0], size: 0 },
|
||||
{ bounds: [4], size: 4 },
|
||||
{ bounds: [1, 2, 4, 8], size: 1 },
|
||||
{ bounds: [1, Math.SQRT2, 2, 8, 16], size: 0.5 }
|
||||
];
|
||||
});
|
||||
|
||||
it('should properly calculate bucket size', () => {
|
||||
_.each(ctx.bounds_set, (b) => {
|
||||
let bucketSize = calculateBucketSize(b.bounds, ctx.logBase);
|
||||
expect(isEqual(bucketSize, b.size)).to.be(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('HeatmapDataConverter', () => {
|
||||
let ctx: any = {};
|
||||
|
||||
@@ -184,9 +233,21 @@ describe('ES Histogram converter', () => {
|
||||
};
|
||||
|
||||
let heatmap = elasticHistogramToHeatmap(ctx.series);
|
||||
console.log(heatmap);
|
||||
expect(isHeatmapDataEqual(heatmap, expectedHeatmap)).to.be(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Compare two numbers with given precision. Suitable for compare float numbers after conversions with precision loss.
|
||||
* @param a
|
||||
* @param b
|
||||
* @param precision
|
||||
*/
|
||||
function isEqual(a: number, b: number, precision = 0.000001): boolean {
|
||||
if (a === b) {
|
||||
return true;
|
||||
} else {
|
||||
return Math.abs(1 - a / b) <= precision;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user