diff --git a/public/app/plugins/panel/graph/axes_editor.html b/public/app/plugins/panel/graph/axes_editor.html
index 6ec64015746..ced0d8157e9 100644
--- a/public/app/plugins/panel/graph/axes_editor.html
+++ b/public/app/plugins/panel/graph/axes_editor.html
@@ -67,6 +67,17 @@
+
+
Y-Axes
diff --git a/public/app/plugins/panel/graph/graph.ts b/public/app/plugins/panel/graph/graph.ts
index 4033e4b3778..e3eed6fc382 100755
--- a/public/app/plugins/panel/graph/graph.ts
+++ b/public/app/plugins/panel/graph/graph.ts
@@ -337,9 +337,17 @@ class GraphElement {
let bucketSize: number;
if (this.data.length) {
- const histMin = _.min(_.map(this.data, s => s.stats.min));
- const histMax = _.max(_.map(this.data, s => s.stats.max));
+ let histMin = _.min(_.map(this.data, s => s.stats.min));
+ let histMax = _.max(_.map(this.data, s => s.stats.max));
const ticks = panel.xaxis.buckets || this.panelWidth / 50;
+ if (panel.xaxis.min != null) {
+ const isInvalidXaxisMin = tickStep(panel.xaxis.min, histMax, ticks) <= 0;
+ histMin = isInvalidXaxisMin ? histMin : panel.xaxis.min;
+ }
+ if (panel.xaxis.max != null) {
+ const isInvalidXaxisMax = tickStep(histMin, panel.xaxis.max, ticks) <= 0;
+ histMax = isInvalidXaxisMax ? histMax : panel.xaxis.max;
+ }
bucketSize = tickStep(histMin, histMax, ticks);
options.series.bars.barWidth = bucketSize * 0.8;
this.data = convertToHistogramData(this.data, bucketSize, this.ctrl.hiddenSeries, histMin, histMax);
diff --git a/public/app/plugins/panel/graph/histogram.ts b/public/app/plugins/panel/graph/histogram.ts
index b09226d15e7..b5c478198c0 100644
--- a/public/app/plugins/panel/graph/histogram.ts
+++ b/public/app/plugins/panel/graph/histogram.ts
@@ -43,6 +43,10 @@ export function convertValuesToHistogram(values: number[], bucketSize: number, m
}
for (let i = 0; i < values.length; i++) {
+ // filter out values outside the min and max boundaries
+ if (values[i] < min || values[i] > max) {
+ continue;
+ }
const bound = getBucketBound(values[i], bucketSize);
histogram[bound] = histogram[bound] + 1;
}
diff --git a/public/app/plugins/panel/graph/specs/graph.test.ts b/public/app/plugins/panel/graph/specs/graph.test.ts
index 58a35ea2a5f..ff81a8ae6eb 100644
--- a/public/app/plugins/panel/graph/specs/graph.test.ts
+++ b/public/app/plugins/panel/graph/specs/graph.test.ts
@@ -516,4 +516,408 @@ describe('grafanaGraph', () => {
expect(ctx.plotData[0].data[0][1]).toBe(2);
});
});
+
+ describe('when graph is histogram, and xaxis min is set', () => {
+ beforeEach(() => {
+ setupCtx(() => {
+ ctrl.panel.xaxis.mode = 'histogram';
+ ctrl.panel.xaxis.min = 150;
+ ctrl.panel.stack = false;
+ ctrl.hiddenSeries = {};
+ ctx.data[0] = new TimeSeries({
+ datapoints: [[100, 1], [100, 2], [200, 3], [300, 4]],
+ alias: 'series1',
+ });
+ });
+ });
+
+ it('should not contain values lower than min', () => {
+ const nonZero = ctx.plotData[0].data.filter(t => t[1] > 0);
+ expect(Math.min.apply(Math, nonZero.map(t => t[0]))).toBe(200);
+ expect(Math.max.apply(Math, nonZero.map(t => t[0]))).toBe(300);
+ });
+ });
+
+ describe('when graph is histogram, and xaxis min is zero', () => {
+ beforeEach(() => {
+ setupCtx(() => {
+ ctrl.panel.xaxis.mode = 'histogram';
+ ctrl.panel.xaxis.min = 0;
+ ctrl.panel.stack = false;
+ ctrl.hiddenSeries = {};
+ ctx.data[0] = new TimeSeries({
+ datapoints: [[-100, 1], [100, 2], [200, 3], [300, 4]],
+ alias: 'series1',
+ });
+ });
+ });
+
+ it('should not contain values lower than zero', () => {
+ const nonZero = ctx.plotData[0].data.filter(t => t[1] > 0);
+ expect(Math.min.apply(Math, nonZero.map(t => t[0]))).toBe(100);
+ expect(Math.max.apply(Math, nonZero.map(t => t[0]))).toBe(300);
+ });
+ });
+
+ describe('when graph is histogram, and xaxis min is null', () => {
+ beforeEach(() => {
+ setupCtx(() => {
+ ctrl.panel.xaxis.mode = 'histogram';
+ ctrl.panel.xaxis.min = null;
+ ctrl.panel.stack = false;
+ ctrl.hiddenSeries = {};
+ ctx.data[0] = new TimeSeries({
+ datapoints: [[-100, 1], [100, 2], [200, 3], [300, 4]],
+ alias: 'series1',
+ });
+ });
+ });
+
+ it('xaxis min should not affect the histogram', () => {
+ const nonZero = ctx.plotData[0].data.filter(t => t[1] > 0);
+ expect(Math.min.apply(Math, nonZero.map(t => t[0]))).toBe(-100);
+ expect(Math.max.apply(Math, nonZero.map(t => t[0]))).toBe(300);
+ });
+ });
+
+ describe('when graph is histogram, and xaxis min is undefined', () => {
+ beforeEach(() => {
+ setupCtx(() => {
+ ctrl.panel.xaxis.mode = 'histogram';
+ ctrl.panel.xaxis.min = undefined;
+ ctrl.panel.stack = false;
+ ctrl.hiddenSeries = {};
+ ctx.data[0] = new TimeSeries({
+ datapoints: [[-100, 1], [100, 2], [200, 3], [300, 4]],
+ alias: 'series1',
+ });
+ });
+ });
+
+ it('xaxis min should not affect the histogram', () => {
+ const nonZero = ctx.plotData[0].data.filter(t => t[1] > 0);
+ expect(Math.min.apply(Math, nonZero.map(t => t[0]))).toBe(-100);
+ expect(Math.max.apply(Math, nonZero.map(t => t[0]))).toBe(300);
+ });
+ });
+
+ describe('when graph is histogram, and xaxis max is set', () => {
+ beforeEach(() => {
+ setupCtx(() => {
+ ctrl.panel.xaxis.mode = 'histogram';
+ ctrl.panel.xaxis.max = 250;
+ ctrl.panel.stack = false;
+ ctrl.hiddenSeries = {};
+ ctx.data[0] = new TimeSeries({
+ datapoints: [[100, 1], [100, 2], [200, 3], [300, 4]],
+ alias: 'series1',
+ });
+ });
+ });
+
+ it('should not contain values greater than max', () => {
+ const nonZero = ctx.plotData[0].data.filter(t => t[1] > 0);
+ expect(Math.min.apply(Math, nonZero.map(t => t[0]))).toBe(100);
+ expect(Math.max.apply(Math, nonZero.map(t => t[0]))).toBe(200);
+ });
+ });
+
+ describe('when graph is histogram, and xaxis max is zero', () => {
+ beforeEach(() => {
+ setupCtx(() => {
+ ctrl.panel.xaxis.mode = 'histogram';
+ ctrl.panel.xaxis.max = 0;
+ ctrl.panel.stack = false;
+ ctrl.hiddenSeries = {};
+ ctx.data[0] = new TimeSeries({
+ datapoints: [[-100, 1], [100, 1], [100, 2], [200, 3], [300, 4]],
+ alias: 'series1',
+ });
+ });
+ });
+
+ it('should not contain values greater than zero', () => {
+ const nonZero = ctx.plotData[0].data.filter(t => t[1] > 0);
+ expect(Math.min.apply(Math, nonZero.map(t => t[0]))).toBe(-100);
+ expect(Math.max.apply(Math, nonZero.map(t => t[0]))).toBe(-100);
+ });
+ });
+
+ describe('when graph is histogram, and xaxis max is null', () => {
+ beforeEach(() => {
+ setupCtx(() => {
+ ctrl.panel.xaxis.mode = 'histogram';
+ ctrl.panel.xaxis.max = null;
+ ctrl.panel.stack = false;
+ ctrl.hiddenSeries = {};
+ ctx.data[0] = new TimeSeries({
+ datapoints: [[-100, 1], [100, 1], [100, 2], [200, 3], [300, 4]],
+ alias: 'series1',
+ });
+ });
+ });
+
+ it('xaxis max should not affect the histogram', () => {
+ const nonZero = ctx.plotData[0].data.filter(t => t[1] > 0);
+ expect(Math.min.apply(Math, nonZero.map(t => t[0]))).toBe(-100);
+ expect(Math.max.apply(Math, nonZero.map(t => t[0]))).toBe(300);
+ });
+ });
+
+ describe('when graph is histogram, and xaxis max is undefined', () => {
+ beforeEach(() => {
+ setupCtx(() => {
+ ctrl.panel.xaxis.mode = 'histogram';
+ ctrl.panel.xaxis.max = undefined;
+ ctrl.panel.stack = false;
+ ctrl.hiddenSeries = {};
+ ctx.data[0] = new TimeSeries({
+ datapoints: [[-100, 1], [100, 1], [100, 2], [200, 3], [300, 4]],
+ alias: 'series1',
+ });
+ });
+ });
+
+ it('xaxis max should not should node affect the histogram', () => {
+ const nonZero = ctx.plotData[0].data.filter(t => t[1] > 0);
+ expect(Math.min.apply(Math, nonZero.map(t => t[0]))).toBe(-100);
+ expect(Math.max.apply(Math, nonZero.map(t => t[0]))).toBe(300);
+ });
+ });
+
+ describe('when graph is histogram, and xaxis min and max are set', () => {
+ beforeEach(() => {
+ setupCtx(() => {
+ ctrl.panel.xaxis.mode = 'histogram';
+ ctrl.panel.xaxis.min = 150;
+ ctrl.panel.xaxis.max = 250;
+ ctrl.panel.stack = false;
+ ctrl.hiddenSeries = {};
+ ctx.data[0] = new TimeSeries({
+ datapoints: [[100, 1], [100, 2], [200, 3], [300, 4]],
+ alias: 'series1',
+ });
+ });
+ });
+
+ it('should not contain values lower than min and greater than max', () => {
+ const nonZero = ctx.plotData[0].data.filter(t => t[1] > 0);
+ expect(Math.min.apply(Math, nonZero.map(t => t[0]))).toBe(200);
+ expect(Math.max.apply(Math, nonZero.map(t => t[0]))).toBe(200);
+ });
+ });
+
+ describe('when graph is histogram, and xaxis min and max are zero', () => {
+ beforeEach(() => {
+ setupCtx(() => {
+ ctrl.panel.xaxis.mode = 'histogram';
+ ctrl.panel.xaxis.min = 0;
+ ctrl.panel.xaxis.max = 0;
+ ctrl.panel.stack = false;
+ ctrl.hiddenSeries = {};
+ ctx.data[0] = new TimeSeries({
+ datapoints: [[-100, 1], [100, 1], [100, 2], [200, 3], [300, 4]],
+ alias: 'series1',
+ });
+ });
+ });
+
+ it('xaxis max should be ignored otherwise the bucketSize is zero', () => {
+ const nonZero = ctx.plotData[0].data.filter(t => t[1] > 0);
+ expect(Math.min.apply(Math, nonZero.map(t => t[0]))).toBe(100);
+ expect(Math.max.apply(Math, nonZero.map(t => t[0]))).toBe(300);
+ });
+ });
+
+ describe('when graph is histogram, and xaxis min and max are null', () => {
+ beforeEach(() => {
+ setupCtx(() => {
+ ctrl.panel.xaxis.mode = 'histogram';
+ ctrl.panel.xaxis.min = null;
+ ctrl.panel.xaxis.max = null;
+ ctrl.panel.stack = false;
+ ctrl.hiddenSeries = {};
+ ctx.data[0] = new TimeSeries({
+ datapoints: [[100, 1], [100, 2], [200, 3], [300, 4]],
+ alias: 'series1',
+ });
+ });
+ });
+
+ it('xaxis min and max should not affect the histogram', () => {
+ const nonZero = ctx.plotData[0].data.filter(t => t[1] > 0);
+ expect(Math.min.apply(Math, nonZero.map(t => t[0]))).toBe(100);
+ expect(Math.max.apply(Math, nonZero.map(t => t[0]))).toBe(300);
+ });
+ });
+
+ describe('when graph is histogram, and xaxis min and max are undefined', () => {
+ beforeEach(() => {
+ setupCtx(() => {
+ ctrl.panel.xaxis.mode = 'histogram';
+ ctrl.panel.xaxis.min = undefined;
+ ctrl.panel.xaxis.max = undefined;
+ ctrl.panel.stack = false;
+ ctrl.hiddenSeries = {};
+ ctx.data[0] = new TimeSeries({
+ datapoints: [[100, 1], [100, 2], [200, 3], [300, 4]],
+ alias: 'series1',
+ });
+ });
+ });
+
+ it('xaxis min and max should not affect the histogram', () => {
+ const nonZero = ctx.plotData[0].data.filter(t => t[1] > 0);
+ expect(Math.min.apply(Math, nonZero.map(t => t[0]))).toBe(100);
+ expect(Math.max.apply(Math, nonZero.map(t => t[0]))).toBe(300);
+ });
+ });
+
+ describe('when graph is histogram, and xaxis min is greater than xaxis max', () => {
+ beforeEach(() => {
+ setupCtx(() => {
+ ctrl.panel.xaxis.mode = 'histogram';
+ ctrl.panel.xaxis.min = 150;
+ ctrl.panel.xaxis.max = 100;
+ ctrl.panel.stack = false;
+ ctrl.hiddenSeries = {};
+ ctx.data[0] = new TimeSeries({
+ datapoints: [[100, 1], [100, 2], [200, 3], [300, 4]],
+ alias: 'series1',
+ });
+ });
+ });
+
+ it('xaxis max should be ignored otherwise the bucketSize is negative', () => {
+ const nonZero = ctx.plotData[0].data.filter(t => t[1] > 0);
+ expect(Math.min.apply(Math, nonZero.map(t => t[0]))).toBe(200);
+ expect(Math.max.apply(Math, nonZero.map(t => t[0]))).toBe(300);
+ });
+ });
+
+ // aaa
+ describe('when graph is histogram, and xaxis min is greater than the maximum value', () => {
+ beforeEach(() => {
+ setupCtx(() => {
+ ctrl.panel.xaxis.mode = 'histogram';
+ ctrl.panel.xaxis.min = 301;
+ ctrl.panel.stack = false;
+ ctrl.hiddenSeries = {};
+ ctx.data[0] = new TimeSeries({
+ datapoints: [[100, 1], [100, 2], [200, 3], [300, 4]],
+ alias: 'series1',
+ });
+ });
+ });
+
+ it('xaxis min should be ignored otherwise the bucketSize is negative', () => {
+ const nonZero = ctx.plotData[0].data.filter(t => t[1] > 0);
+ expect(Math.min.apply(Math, nonZero.map(t => t[0]))).toBe(100);
+ expect(Math.max.apply(Math, nonZero.map(t => t[0]))).toBe(300);
+ });
+ });
+
+ describe('when graph is histogram, and xaxis min is equal to the maximum value', () => {
+ beforeEach(() => {
+ setupCtx(() => {
+ ctrl.panel.xaxis.mode = 'histogram';
+ ctrl.panel.xaxis.min = 300;
+ ctrl.panel.stack = false;
+ ctrl.hiddenSeries = {};
+ ctx.data[0] = new TimeSeries({
+ datapoints: [[100, 1], [100, 2], [200, 3], [300, 4]],
+ alias: 'series1',
+ });
+ });
+ });
+
+ it('xaxis min should be ignored otherwise the bucketSize is zero', () => {
+ const nonZero = ctx.plotData[0].data.filter(t => t[1] > 0);
+ expect(Math.min.apply(Math, nonZero.map(t => t[0]))).toBe(100);
+ expect(Math.max.apply(Math, nonZero.map(t => t[0]))).toBe(300);
+ });
+ });
+
+ describe('when graph is histogram, and xaxis min is lower than the minimum value', () => {
+ beforeEach(() => {
+ setupCtx(() => {
+ ctrl.panel.xaxis.mode = 'histogram';
+ ctrl.panel.xaxis.min = 99;
+ ctrl.panel.stack = false;
+ ctrl.hiddenSeries = {};
+ ctx.data[0] = new TimeSeries({
+ datapoints: [[100, 1], [100, 2], [200, 3], [300, 4]],
+ alias: 'series1',
+ });
+ });
+ });
+
+ it('xaxis min should not affect the histogram', () => {
+ const nonZero = ctx.plotData[0].data.filter(t => t[1] > 0);
+ expect(Math.min.apply(Math, nonZero.map(t => t[0]))).toBe(100);
+ expect(Math.max.apply(Math, nonZero.map(t => t[0]))).toBe(300);
+ });
+ });
+
+ describe('when graph is histogram, and xaxis max is equal to the minimum value', () => {
+ beforeEach(() => {
+ setupCtx(() => {
+ ctrl.panel.xaxis.mode = 'histogram';
+ ctrl.panel.xaxis.max = 100;
+ ctrl.panel.stack = false;
+ ctrl.hiddenSeries = {};
+ ctx.data[0] = new TimeSeries({
+ datapoints: [[100, 1], [100, 2], [200, 3], [300, 4]],
+ alias: 'series1',
+ });
+ });
+ });
+
+ it('should calculate correct histogram', () => {
+ const nonZero = ctx.plotData[0].data.filter(t => t[1] > 0);
+ expect(Math.min.apply(Math, nonZero.map(t => t[0]))).toBe(100);
+ expect(Math.max.apply(Math, nonZero.map(t => t[0]))).toBe(100);
+ });
+ });
+
+ describe('when graph is histogram, and xaxis max is a lower than the minimum value', () => {
+ beforeEach(() => {
+ setupCtx(() => {
+ ctrl.panel.xaxis.mode = 'histogram';
+ ctrl.panel.xaxis.max = 99;
+ ctrl.panel.stack = false;
+ ctrl.hiddenSeries = {};
+ ctx.data[0] = new TimeSeries({
+ datapoints: [[100, 1], [100, 2], [200, 3], [300, 4]],
+ alias: 'series1',
+ });
+ });
+ });
+
+ it('should calculate empty histogram', () => {
+ const nonZero = ctx.plotData[0].data.filter(t => t[1] > 0);
+ expect(nonZero.length).toBe(0);
+ });
+ });
+
+ describe('when graph is histogram, and xaxis max is greater than the maximum value', () => {
+ beforeEach(() => {
+ setupCtx(() => {
+ ctrl.panel.xaxis.mode = 'histogram';
+ ctrl.panel.xaxis.max = 301;
+ ctrl.panel.stack = false;
+ ctrl.hiddenSeries = {};
+ ctx.data[0] = new TimeSeries({
+ datapoints: [[100, 1], [100, 2], [200, 3], [300, 4]],
+ alias: 'series1',
+ });
+ });
+ });
+
+ it('should calculate correct histogram', () => {
+ const nonZero = ctx.plotData[0].data.filter(t => t[1] > 0);
+ expect(Math.min.apply(Math, nonZero.map(t => t[0]))).toBe(100);
+ expect(Math.max.apply(Math, nonZero.map(t => t[0]))).toBe(300);
+ });
+ });
});