Commit 38a19f7c by Alexander Zobnin

Merge branch 'histogram_multi' of https://github.com/mtanda/grafana into mtanda-histogram_multi

parents b400f7cc cf1be5fd
...@@ -29,12 +29,17 @@ export class DataProcessor { ...@@ -29,12 +29,17 @@ export class DataProcessor {
}); });
} }
case 'histogram': { case 'histogram': {
let histogramDataList = [ let histogramDataList;
if (this.panel.stack) {
histogramDataList = options.dataList;
} else {
histogramDataList = [
{ {
target: 'count', target: 'count',
datapoints: _.concat([], _.flatten(_.map(options.dataList, 'datapoints'))), datapoints: _.concat([], _.flatten(_.map(options.dataList, 'datapoints'))),
}, },
]; ];
}
return histogramDataList.map((item, index) => { return histogramDataList.map((item, index) => {
return this.timeSeriesHandler(item, index, options); return this.timeSeriesHandler(item, index, options);
}); });
......
...@@ -17,7 +17,7 @@ import { appEvents, coreModule, updateLegendValues } from 'app/core/core'; ...@@ -17,7 +17,7 @@ import { appEvents, coreModule, updateLegendValues } from 'app/core/core';
import GraphTooltip from './graph_tooltip'; import GraphTooltip from './graph_tooltip';
import { ThresholdManager } from './threshold_manager'; import { ThresholdManager } from './threshold_manager';
import { EventManager } from 'app/features/annotations/all'; import { EventManager } from 'app/features/annotations/all';
import { convertValuesToHistogram, getSeriesValues } from './histogram'; import { convertToHistogramData } from './histogram';
import config from 'app/core/config'; import config from 'app/core/config';
/** @ngInject **/ /** @ngInject **/
...@@ -236,15 +236,15 @@ function graphDirective(timeSrv, popoverSrv, contextSrv) { ...@@ -236,15 +236,15 @@ function graphDirective(timeSrv, popoverSrv, contextSrv) {
} }
case 'histogram': { case 'histogram': {
let bucketSize: number; let bucketSize: number;
let values = getSeriesValues(data);
if (data.length && values.length) { if (data.length) {
let histMin = _.min(_.map(data, s => s.stats.min)); let histMin = _.min(_.map(data, s => s.stats.min));
let histMax = _.max(_.map(data, s => s.stats.max)); let histMax = _.max(_.map(data, s => s.stats.max));
let ticks = panel.xaxis.buckets || panelWidth / 50; let ticks = panel.xaxis.buckets || panelWidth / 50;
bucketSize = tickStep(histMin, histMax, ticks); bucketSize = tickStep(histMin, histMax, ticks);
let histogram = convertValuesToHistogram(values, bucketSize);
data[0].data = histogram; data = convertToHistogramData(data, bucketSize, ctrl.hiddenSeries, panel.stack, histMin, histMax);
options.series.bars.barWidth = bucketSize * 0.8; options.series.bars.barWidth = bucketSize * 0.8;
} else { } else {
bucketSize = 0; bucketSize = 0;
...@@ -413,7 +413,13 @@ function graphDirective(timeSrv, popoverSrv, contextSrv) { ...@@ -413,7 +413,13 @@ function graphDirective(timeSrv, popoverSrv, contextSrv) {
let defaultTicks = panelWidth / 50; let defaultTicks = panelWidth / 50;
if (data.length && bucketSize) { if (data.length && bucketSize) {
ticks = _.map(data[0].data, point => point[0]); let tick_values = [];
for (let d of data) {
for (let point of d.data) {
tick_values[point[0]] = true;
}
}
ticks = Object.keys(tick_values).map(v => Number(v));
min = _.min(ticks); min = _.min(ticks);
max = _.max(ticks); max = _.max(ticks);
......
...@@ -29,16 +29,22 @@ export function getSeriesValues(dataList: TimeSeries[]): number[] { ...@@ -29,16 +29,22 @@ export function getSeriesValues(dataList: TimeSeries[]): number[] {
* @param values * @param values
* @param bucketSize * @param bucketSize
*/ */
export function convertValuesToHistogram(values: number[], bucketSize: number): any[] { export function convertValuesToHistogram(values: number[], bucketSize: number, min: number, max: number): any[] {
let histogram = {}; let histogram = {};
let minBound = getBucketBound(min, bucketSize);
let maxBound = getBucketBound(max, bucketSize);
let bound = minBound;
let n = 0;
while (bound <= maxBound) {
histogram[bound] = 0;
bound = minBound + bucketSize * n;
n++;
}
for (let i = 0; i < values.length; i++) { for (let i = 0; i < values.length; i++) {
let bound = getBucketBound(values[i], bucketSize); let bound = getBucketBound(values[i], bucketSize);
if (histogram[bound]) {
histogram[bound] = histogram[bound] + 1; histogram[bound] = histogram[bound] + 1;
} else {
histogram[bound] = 1;
}
} }
let histogam_series = _.map(histogram, (count, bound) => { let histogam_series = _.map(histogram, (count, bound) => {
...@@ -49,6 +55,33 @@ export function convertValuesToHistogram(values: number[], bucketSize: number): ...@@ -49,6 +55,33 @@ export function convertValuesToHistogram(values: number[], bucketSize: number):
return _.sortBy(histogam_series, point => point[0]); return _.sortBy(histogam_series, point => point[0]);
} }
/**
* Convert series into array of histogram data.
* @param data Array of series
* @param bucketSize
* @param stack
*/
export function convertToHistogramData(
data: any,
bucketSize: number,
hiddenSeries: any,
stack = false,
min: number,
max: number
): any[] {
return data.map(series => {
let values = getSeriesValues([series]);
series.histogram = true;
if (!hiddenSeries[series.alias]) {
let histogram = convertValuesToHistogram(values, bucketSize, min, max);
series.data = histogram;
} else {
series.data = [];
}
return series;
});
}
function getBucketBound(value: number, bucketSize: number): number { function getBucketBound(value: number, bucketSize: number): number {
return Math.floor(value / bucketSize) * bucketSize; return Math.floor(value / bucketSize) * bucketSize;
} }
...@@ -407,4 +407,48 @@ describe('grafanaGraph', function() { ...@@ -407,4 +407,48 @@ describe('grafanaGraph', function() {
}, },
10 10
); );
graphScenario('when graph is histogram, and enable stack', function(ctx) {
ctx.setup(function(ctrl, data) {
ctrl.panel.xaxis.mode = 'histogram';
ctrl.panel.stack = true;
ctrl.hiddenSeries = {};
data[0] = new TimeSeries({
datapoints: [[100, 1], [100, 2], [200, 3], [300, 4]],
alias: 'series1',
});
data[1] = new TimeSeries({
datapoints: [[100, 1], [100, 2], [200, 3], [300, 4]],
alias: 'series2',
});
});
it('should calculate correct histogram', function() {
expect(ctx.plotData[0].data[0][0]).to.be(100);
expect(ctx.plotData[0].data[0][1]).to.be(2);
expect(ctx.plotData[1].data[0][0]).to.be(100);
expect(ctx.plotData[1].data[0][1]).to.be(2);
});
});
graphScenario('when graph is histogram, and some series are hidden', function(ctx) {
ctx.setup(function(ctrl, data) {
ctrl.panel.xaxis.mode = 'histogram';
ctrl.panel.stack = false;
ctrl.hiddenSeries = { series2: true };
data[0] = new TimeSeries({
datapoints: [[100, 1], [100, 2], [200, 3], [300, 4]],
alias: 'series1',
});
data[1] = new TimeSeries({
datapoints: [[100, 1], [100, 2], [200, 3], [300, 4]],
alias: 'series2',
});
});
it('should calculate correct histogram', function() {
expect(ctx.plotData[0].data[0][0]).to.be(100);
expect(ctx.plotData[0].data[0][1]).to.be(2);
});
});
}); });
...@@ -13,15 +13,15 @@ describe('Graph Histogam Converter', function() { ...@@ -13,15 +13,15 @@ describe('Graph Histogam Converter', function() {
bucketSize = 10; bucketSize = 10;
let expected = [[0, 2], [10, 3], [20, 2]]; let expected = [[0, 2], [10, 3], [20, 2]];
let histogram = convertValuesToHistogram(values, bucketSize); let histogram = convertValuesToHistogram(values, bucketSize, 1, 29);
expect(histogram).toMatchObject(expected); expect(histogram).toMatchObject(expected);
}); });
it('Should not add empty buckets', () => { it('Should not add empty buckets', () => {
bucketSize = 5; bucketSize = 5;
let expected = [[0, 2], [10, 2], [15, 1], [20, 1], [25, 1]]; let expected = [[0, 2], [5, 0], [10, 2], [15, 1], [20, 1], [25, 1]];
let histogram = convertValuesToHistogram(values, bucketSize); let histogram = convertValuesToHistogram(values, bucketSize, 1, 29);
expect(histogram).toMatchObject(expected); expect(histogram).toMatchObject(expected);
}); });
}); });
......
...@@ -71,7 +71,7 @@ ...@@ -71,7 +71,7 @@
<div class="section gf-form-group"> <div class="section gf-form-group">
<h5 class="section-heading">Stacking & Null value</h5> <h5 class="section-heading">Stacking & Null value</h5>
<gf-form-switch class="gf-form" label="Stack" label-class="width-7" checked="ctrl.panel.stack" on-change="ctrl.render()"> <gf-form-switch class="gf-form" label="Stack" label-class="width-7" checked="ctrl.panel.stack" on-change="ctrl.refresh()">
</gf-form-switch> </gf-form-switch>
<gf-form-switch class="gf-form" ng-show="ctrl.panel.stack" label="Percent" label-class="width-7" checked="ctrl.panel.percentage" on-change="ctrl.render()"> <gf-form-switch class="gf-form" ng-show="ctrl.panel.stack" label="Percent" label-class="width-7" checked="ctrl.panel.percentage" on-change="ctrl.render()">
</gf-form-switch> </gf-form-switch>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment