소스 검색

heatmap: add function for bucket size calculation

Alexander Zobnin 8 년 전
부모
커밋
cb136c07b8
2개의 변경된 파일112개의 추가작업 그리고 6개의 파일을 삭제
  1. 49 4
      public/app/plugins/panel/heatmap/heatmap_data_converter.ts
  2. 63 2
      public/app/plugins/panel/heatmap/specs/heatmap_data_converter_specs.ts

+ 49 - 4
public/app/plugins/panel/heatmap/heatmap_data_converter.ts

@@ -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
 };

+ 63 - 2
public/app/plugins/panel/heatmap/specs/heatmap_data_converter_specs.ts

@@ -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;
+  }
+}