diff --git a/public/app/plugins/panel/graph/data_processor.ts b/public/app/plugins/panel/graph/data_processor.ts index 2930c871bc9..f8162c57a10 100644 --- a/public/app/plugins/panel/graph/data_processor.ts +++ b/public/app/plugins/panel/graph/data_processor.ts @@ -29,12 +29,17 @@ export class DataProcessor { }); } case 'histogram': { - let histogramDataList = [ - { - target: 'count', - datapoints: _.concat([], _.flatten(_.map(options.dataList, 'datapoints'))), - }, - ]; + let histogramDataList; + if (this.panel.stack) { + histogramDataList = options.dataList; + } else { + histogramDataList = [ + { + target: 'count', + datapoints: _.concat([], _.flatten(_.map(options.dataList, 'datapoints'))), + }, + ]; + } return histogramDataList.map((item, index) => { return this.timeSeriesHandler(item, index, options); }); diff --git a/public/app/plugins/panel/graph/graph.ts b/public/app/plugins/panel/graph/graph.ts index 3ed8cbc1836..5edd5c38aac 100755 --- a/public/app/plugins/panel/graph/graph.ts +++ b/public/app/plugins/panel/graph/graph.ts @@ -17,7 +17,7 @@ import { appEvents, coreModule, updateLegendValues } from 'app/core/core'; import GraphTooltip from './graph_tooltip'; import { ThresholdManager } from './threshold_manager'; import { EventManager } from 'app/features/annotations/all'; -import { convertValuesToHistogram, getSeriesValues } from './histogram'; +import { convertToHistogramData } from './histogram'; import config from 'app/core/config'; /** @ngInject **/ @@ -236,15 +236,15 @@ function graphDirective(timeSrv, popoverSrv, contextSrv) { } case 'histogram': { 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 histMax = _.max(_.map(data, s => s.stats.max)); let ticks = panel.xaxis.buckets || panelWidth / 50; 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; } else { bucketSize = 0; @@ -413,7 +413,13 @@ function graphDirective(timeSrv, popoverSrv, contextSrv) { let defaultTicks = panelWidth / 50; 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); max = _.max(ticks); diff --git a/public/app/plugins/panel/graph/histogram.ts b/public/app/plugins/panel/graph/histogram.ts index 8b2be9efcf7..b8867c999cc 100644 --- a/public/app/plugins/panel/graph/histogram.ts +++ b/public/app/plugins/panel/graph/histogram.ts @@ -29,16 +29,22 @@ export function getSeriesValues(dataList: TimeSeries[]): number[] { * @param values * @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 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++) { let bound = getBucketBound(values[i], bucketSize); - if (histogram[bound]) { - histogram[bound] = histogram[bound] + 1; - } else { - histogram[bound] = 1; - } + histogram[bound] = histogram[bound] + 1; } let histogam_series = _.map(histogram, (count, bound) => { @@ -49,6 +55,33 @@ export function convertValuesToHistogram(values: number[], bucketSize: number): 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 { return Math.floor(value / bucketSize) * bucketSize; } diff --git a/public/app/plugins/panel/graph/specs/graph_specs.ts b/public/app/plugins/panel/graph/specs/graph_specs.ts index 176bb65b806..d29320a9d72 100644 --- a/public/app/plugins/panel/graph/specs/graph_specs.ts +++ b/public/app/plugins/panel/graph/specs/graph_specs.ts @@ -407,4 +407,48 @@ describe('grafanaGraph', function() { }, 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); + }); + }); }); diff --git a/public/app/plugins/panel/graph/specs/histogram.jest.ts b/public/app/plugins/panel/graph/specs/histogram.jest.ts index 4f0ca472375..0e9eaa8b98e 100644 --- a/public/app/plugins/panel/graph/specs/histogram.jest.ts +++ b/public/app/plugins/panel/graph/specs/histogram.jest.ts @@ -13,15 +13,15 @@ describe('Graph Histogam Converter', function() { bucketSize = 10; let expected = [[0, 2], [10, 3], [20, 2]]; - let histogram = convertValuesToHistogram(values, bucketSize); + let histogram = convertValuesToHistogram(values, bucketSize, 1, 29); expect(histogram).toMatchObject(expected); }); it('Should not add empty buckets', () => { 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); }); }); diff --git a/public/app/plugins/panel/graph/tab_display.html b/public/app/plugins/panel/graph/tab_display.html index 2ab5418c6c2..ebc6cf9b18e 100644 --- a/public/app/plugins/panel/graph/tab_display.html +++ b/public/app/plugins/panel/graph/tab_display.html @@ -71,7 +71,7 @@