mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge pull request #10994 from ilgizar/1271_share_zero
This commit is contained in:
commit
1ee0d1c296
154
public/app/plugins/panel/graph/align_yaxes.ts
Normal file
154
public/app/plugins/panel/graph/align_yaxes.ts
Normal file
@ -0,0 +1,154 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
/**
|
||||
* To align two Y axes by Y level
|
||||
* @param yAxes data [{min: min_y1, min: max_y1}, {min: min_y2, max: max_y2}]
|
||||
* @param level Y level
|
||||
*/
|
||||
export function alignYLevel(yAxes, level) {
|
||||
if (isNaN(level) || !checkCorrectAxis(yAxes)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var [yLeft, yRight] = yAxes;
|
||||
moveLevelToZero(yLeft, yRight, level);
|
||||
|
||||
expandStuckValues(yLeft, yRight);
|
||||
|
||||
// one of graphs on zero
|
||||
var zero = yLeft.min === 0 || yRight.min === 0 || yLeft.max === 0 || yRight.max === 0;
|
||||
|
||||
var oneSide = checkOneSide(yLeft, yRight);
|
||||
|
||||
if (zero && oneSide) {
|
||||
yLeft.min = yLeft.max > 0 ? 0 : yLeft.min;
|
||||
yLeft.max = yLeft.max > 0 ? yLeft.max : 0;
|
||||
yRight.min = yRight.max > 0 ? 0 : yRight.min;
|
||||
yRight.max = yRight.max > 0 ? yRight.max : 0;
|
||||
} else {
|
||||
if (checkOppositeSides(yLeft, yRight)) {
|
||||
if (yLeft.min >= 0) {
|
||||
yLeft.min = -yLeft.max;
|
||||
yRight.max = -yRight.min;
|
||||
} else {
|
||||
yLeft.max = -yLeft.min;
|
||||
yRight.min = -yRight.max;
|
||||
}
|
||||
} else {
|
||||
var rate = getRate(yLeft, yRight);
|
||||
|
||||
if (oneSide) {
|
||||
// all graphs above the Y level
|
||||
if (yLeft.min > 0) {
|
||||
yLeft.min = yLeft.max / rate;
|
||||
yRight.min = yRight.max / rate;
|
||||
} else {
|
||||
yLeft.max = yLeft.min / rate;
|
||||
yRight.max = yRight.min / rate;
|
||||
}
|
||||
} else {
|
||||
if (checkTwoCross(yLeft, yRight)) {
|
||||
yLeft.min = yRight.min ? yRight.min * rate : yLeft.min;
|
||||
yRight.min = yLeft.min ? yLeft.min / rate : yRight.min;
|
||||
yLeft.max = yRight.max ? yRight.max * rate : yLeft.max;
|
||||
yRight.max = yLeft.max ? yLeft.max / rate : yRight.max;
|
||||
} else {
|
||||
yLeft.min = yLeft.min > 0 ? yRight.min * rate : yLeft.min;
|
||||
yRight.min = yRight.min > 0 ? yLeft.min / rate : yRight.min;
|
||||
yLeft.max = yLeft.max < 0 ? yRight.max * rate : yLeft.max;
|
||||
yRight.max = yRight.max < 0 ? yLeft.max / rate : yRight.max;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
restoreLevelFromZero(yLeft, yRight, level);
|
||||
}
|
||||
|
||||
function expandStuckValues(yLeft, yRight) {
|
||||
// wide Y min and max using increased wideFactor
|
||||
var wideFactor = 0.25;
|
||||
if (yLeft.max === yLeft.min) {
|
||||
yLeft.min -= wideFactor;
|
||||
yLeft.max += wideFactor;
|
||||
}
|
||||
if (yRight.max === yRight.min) {
|
||||
yRight.min -= wideFactor;
|
||||
yRight.max += wideFactor;
|
||||
}
|
||||
}
|
||||
|
||||
function moveLevelToZero(yLeft, yRight, level) {
|
||||
if (level !== 0) {
|
||||
yLeft.min -= level;
|
||||
yLeft.max -= level;
|
||||
yRight.min -= level;
|
||||
yRight.max -= level;
|
||||
}
|
||||
}
|
||||
|
||||
function restoreLevelFromZero(yLeft, yRight, level) {
|
||||
if (level !== 0) {
|
||||
yLeft.min += level;
|
||||
yLeft.max += level;
|
||||
yRight.min += level;
|
||||
yRight.max += level;
|
||||
}
|
||||
}
|
||||
|
||||
function checkCorrectAxis(axis) {
|
||||
return axis.length === 2 && checkCorrectAxes(axis[0]) && checkCorrectAxes(axis[1]);
|
||||
}
|
||||
|
||||
function checkCorrectAxes(axes) {
|
||||
return 'min' in axes && 'max' in axes;
|
||||
}
|
||||
|
||||
function checkOneSide(yLeft, yRight) {
|
||||
// on the one hand with respect to zero
|
||||
return (yLeft.min >= 0 && yRight.min >= 0) || (yLeft.max <= 0 && yRight.max <= 0);
|
||||
}
|
||||
|
||||
function checkTwoCross(yLeft, yRight) {
|
||||
// both across zero
|
||||
return yLeft.min <= 0 && yLeft.max >= 0 && yRight.min <= 0 && yRight.max >= 0;
|
||||
}
|
||||
|
||||
function checkOppositeSides(yLeft, yRight) {
|
||||
// on the opposite sides with respect to zero
|
||||
return (yLeft.min >= 0 && yRight.max <= 0) || (yLeft.max <= 0 && yRight.min >= 0);
|
||||
}
|
||||
|
||||
function getRate(yLeft, yRight) {
|
||||
var rateLeft, rateRight, rate;
|
||||
if (checkTwoCross(yLeft, yRight)) {
|
||||
rateLeft = yRight.min ? yLeft.min / yRight.min : 0;
|
||||
rateRight = yRight.max ? yLeft.max / yRight.max : 0;
|
||||
} else {
|
||||
if (checkOneSide(yLeft, yRight)) {
|
||||
var absLeftMin = Math.abs(yLeft.min);
|
||||
var absLeftMax = Math.abs(yLeft.max);
|
||||
var absRightMin = Math.abs(yRight.min);
|
||||
var absRightMax = Math.abs(yRight.max);
|
||||
var upLeft = _.max([absLeftMin, absLeftMax]);
|
||||
var downLeft = _.min([absLeftMin, absLeftMax]);
|
||||
var upRight = _.max([absRightMin, absRightMax]);
|
||||
var downRight = _.min([absRightMin, absRightMax]);
|
||||
|
||||
rateLeft = downLeft ? upLeft / downLeft : upLeft;
|
||||
rateRight = downRight ? upRight / downRight : upRight;
|
||||
} else {
|
||||
if (yLeft.min > 0 || yRight.min > 0) {
|
||||
rateLeft = yLeft.max / yRight.max;
|
||||
rateRight = 0;
|
||||
} else {
|
||||
rateLeft = 0;
|
||||
rateRight = yLeft.min / yRight.min;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rate = rateLeft > rateRight ? rateLeft : rateRight;
|
||||
|
||||
return rate;
|
||||
}
|
@ -11,6 +11,7 @@
|
||||
<label class="gf-form-label width-6">Unit</label>
|
||||
<div class="gf-form-dropdown-typeahead max-width-20" ng-model="yaxis.format" dropdown-typeahead2="ctrl.unitFormats" dropdown-typeahead-on-select="ctrl.setUnitFormat(yaxis, $subItem)"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-6">Scale</label>
|
||||
@ -29,6 +30,8 @@
|
||||
<input type="text" class="gf-form-input width-5" placeholder="auto" empty-to-null ng-model="yaxis.max" ng-change="ctrl.render()" ng-model-onblur>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-if="yaxis.show">
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-6">Decimals</label>
|
||||
<input type="number" class="gf-form-input max-width-20" placeholder="auto" empty-to-null bs-tooltip="'Override automatic decimal precision for y-axis'" data-placement="right" ng-model="yaxis.decimals" ng-change="ctrl.render()" ng-model-onblur>
|
||||
@ -64,6 +67,18 @@
|
||||
<input type="number" class="gf-form-input max-width-8" ng-model="ctrl.panel.xaxis.buckets" placeholder="auto" ng-change="ctrl.render()" ng-model-onblur bs-tooltip="'Number of buckets'" data-placement="right">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<br/>
|
||||
<h5 class="section-heading">Y-Axes</h5>
|
||||
<gf-form-switch class="gf-form" label="Align" tooltip="Align left and right Y-axes" label-class="width-6" switch-class="width-5" checked="ctrl.panel.yaxis.align" on-change="ctrl.render()"></gf-form-switch>
|
||||
<div class="gf-form" ng-show="ctrl.panel.yaxis.align">
|
||||
<label class="gf-form-label width-6">
|
||||
Level
|
||||
</label>
|
||||
<input type="number" class="gf-form-input width-5" placeholder="0" ng-model="ctrl.panel.yaxis.alignLevel" ng-change="ctrl.render()" ng-model-onblur bs-tooltip="'Alignment of Y-axes are based on this value, starting from Y=0'" data-placement="right">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
@ -18,6 +18,7 @@ import GraphTooltip from './graph_tooltip';
|
||||
import { ThresholdManager } from './threshold_manager';
|
||||
import { EventManager } from 'app/features/annotations/all';
|
||||
import { convertToHistogramData } from './histogram';
|
||||
import { alignYLevel } from './align_yaxes';
|
||||
import config from 'app/core/config';
|
||||
|
||||
/** @ngInject **/
|
||||
@ -155,6 +156,16 @@ function graphDirective(timeSrv, popoverSrv, contextSrv) {
|
||||
}
|
||||
}
|
||||
|
||||
function processRangeHook(plot) {
|
||||
var yAxes = plot.getYAxes();
|
||||
const align = panel.yaxis.align || false;
|
||||
|
||||
if (yAxes.length > 1 && align === true) {
|
||||
const level = panel.yaxis.alignLevel || 0;
|
||||
alignYLevel(yAxes, parseFloat(level));
|
||||
}
|
||||
}
|
||||
|
||||
// Series could have different timeSteps,
|
||||
// let's find the smallest one so that bars are correctly rendered.
|
||||
// In addition, only take series which are rendered as bars for this.
|
||||
@ -294,6 +305,7 @@ function graphDirective(timeSrv, popoverSrv, contextSrv) {
|
||||
hooks: {
|
||||
draw: [drawHook],
|
||||
processOffset: [processOffsetHook],
|
||||
processRange: [processRangeHook],
|
||||
},
|
||||
legend: { show: false },
|
||||
series: {
|
||||
|
@ -55,6 +55,10 @@ class GraphCtrl extends MetricsPanelCtrl {
|
||||
values: [],
|
||||
buckets: null,
|
||||
},
|
||||
yaxis: {
|
||||
align: false,
|
||||
alignLevel: null,
|
||||
},
|
||||
// show/hide lines
|
||||
lines: true,
|
||||
// fill factor
|
||||
|
210
public/app/plugins/panel/graph/specs/align_yaxes.jest.ts
Normal file
210
public/app/plugins/panel/graph/specs/align_yaxes.jest.ts
Normal file
@ -0,0 +1,210 @@
|
||||
import { alignYLevel } from '../align_yaxes';
|
||||
|
||||
describe('Graph Y axes aligner', function() {
|
||||
let yaxes, expected;
|
||||
let alignY = 0;
|
||||
|
||||
describe('on the one hand with respect to zero', () => {
|
||||
it('Should shrink Y axis', () => {
|
||||
yaxes = [{ min: 5, max: 10 }, { min: 2, max: 3 }];
|
||||
expected = [{ min: 5, max: 10 }, { min: 1.5, max: 3 }];
|
||||
|
||||
alignYLevel(yaxes, alignY);
|
||||
expect(yaxes).toMatchObject(expected);
|
||||
});
|
||||
|
||||
it('Should shrink Y axis', () => {
|
||||
yaxes = [{ min: 2, max: 3 }, { min: 5, max: 10 }];
|
||||
expected = [{ min: 1.5, max: 3 }, { min: 5, max: 10 }];
|
||||
|
||||
alignYLevel(yaxes, alignY);
|
||||
expect(yaxes).toMatchObject(expected);
|
||||
});
|
||||
|
||||
it('Should shrink Y axis', () => {
|
||||
yaxes = [{ min: -10, max: -5 }, { min: -3, max: -2 }];
|
||||
expected = [{ min: -10, max: -5 }, { min: -3, max: -1.5 }];
|
||||
|
||||
alignYLevel(yaxes, alignY);
|
||||
expect(yaxes).toMatchObject(expected);
|
||||
});
|
||||
|
||||
it('Should shrink Y axis', () => {
|
||||
yaxes = [{ min: -3, max: -2 }, { min: -10, max: -5 }];
|
||||
expected = [{ min: -3, max: -1.5 }, { min: -10, max: -5 }];
|
||||
|
||||
alignYLevel(yaxes, alignY);
|
||||
expect(yaxes).toMatchObject(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('on the opposite sides with respect to zero', () => {
|
||||
it('Should shrink Y axes', () => {
|
||||
yaxes = [{ min: -3, max: -1 }, { min: 5, max: 10 }];
|
||||
expected = [{ min: -3, max: 3 }, { min: -10, max: 10 }];
|
||||
|
||||
alignYLevel(yaxes, alignY);
|
||||
expect(yaxes).toMatchObject(expected);
|
||||
});
|
||||
|
||||
it('Should shrink Y axes', () => {
|
||||
yaxes = [{ min: 1, max: 3 }, { min: -10, max: -5 }];
|
||||
expected = [{ min: -3, max: 3 }, { min: -10, max: 10 }];
|
||||
|
||||
alignYLevel(yaxes, alignY);
|
||||
expect(yaxes).toMatchObject(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('both across zero', () => {
|
||||
it('Should shrink Y axes', () => {
|
||||
yaxes = [{ min: -10, max: 5 }, { min: -2, max: 3 }];
|
||||
expected = [{ min: -10, max: 15 }, { min: -2, max: 3 }];
|
||||
|
||||
alignYLevel(yaxes, alignY);
|
||||
expect(yaxes).toMatchObject(expected);
|
||||
});
|
||||
|
||||
it('Should shrink Y axes', () => {
|
||||
yaxes = [{ min: -5, max: 10 }, { min: -3, max: 2 }];
|
||||
expected = [{ min: -15, max: 10 }, { min: -3, max: 2 }];
|
||||
|
||||
alignYLevel(yaxes, alignY);
|
||||
expect(yaxes).toMatchObject(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('one of graphs on zero', () => {
|
||||
it('Should shrink Y axes', () => {
|
||||
yaxes = [{ min: 0, max: 3 }, { min: 5, max: 10 }];
|
||||
expected = [{ min: 0, max: 3 }, { min: 0, max: 10 }];
|
||||
|
||||
alignYLevel(yaxes, alignY);
|
||||
expect(yaxes).toMatchObject(expected);
|
||||
});
|
||||
|
||||
it('Should shrink Y axes', () => {
|
||||
yaxes = [{ min: 5, max: 10 }, { min: 0, max: 3 }];
|
||||
expected = [{ min: 0, max: 10 }, { min: 0, max: 3 }];
|
||||
|
||||
alignYLevel(yaxes, alignY);
|
||||
expect(yaxes).toMatchObject(expected);
|
||||
});
|
||||
|
||||
it('Should shrink Y axes', () => {
|
||||
yaxes = [{ min: -3, max: 0 }, { min: -10, max: -5 }];
|
||||
expected = [{ min: -3, max: 0 }, { min: -10, max: 0 }];
|
||||
|
||||
alignYLevel(yaxes, alignY);
|
||||
expect(yaxes).toMatchObject(expected);
|
||||
});
|
||||
|
||||
it('Should shrink Y axes', () => {
|
||||
yaxes = [{ min: -10, max: -5 }, { min: -3, max: 0 }];
|
||||
expected = [{ min: -10, max: 0 }, { min: -3, max: 0 }];
|
||||
|
||||
alignYLevel(yaxes, alignY);
|
||||
expect(yaxes).toMatchObject(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('both graphs on zero', () => {
|
||||
it('Should shrink Y axes', () => {
|
||||
yaxes = [{ min: 0, max: 3 }, { min: -10, max: 0 }];
|
||||
expected = [{ min: -3, max: 3 }, { min: -10, max: 10 }];
|
||||
|
||||
alignYLevel(yaxes, alignY);
|
||||
expect(yaxes).toMatchObject(expected);
|
||||
});
|
||||
|
||||
it('Should shrink Y axes', () => {
|
||||
yaxes = [{ min: -3, max: 0 }, { min: 0, max: 10 }];
|
||||
expected = [{ min: -3, max: 3 }, { min: -10, max: 10 }];
|
||||
|
||||
alignYLevel(yaxes, alignY);
|
||||
expect(yaxes).toMatchObject(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mixed placement of graphs relative to zero', () => {
|
||||
it('Should shrink Y axes', () => {
|
||||
yaxes = [{ min: -10, max: 5 }, { min: 1, max: 3 }];
|
||||
expected = [{ min: -10, max: 5 }, { min: -6, max: 3 }];
|
||||
|
||||
alignYLevel(yaxes, alignY);
|
||||
expect(yaxes).toMatchObject(expected);
|
||||
});
|
||||
|
||||
it('Should shrink Y axes', () => {
|
||||
yaxes = [{ min: 1, max: 3 }, { min: -10, max: 5 }];
|
||||
expected = [{ min: -6, max: 3 }, { min: -10, max: 5 }];
|
||||
|
||||
alignYLevel(yaxes, alignY);
|
||||
expect(yaxes).toMatchObject(expected);
|
||||
});
|
||||
|
||||
it('Should shrink Y axes', () => {
|
||||
yaxes = [{ min: -10, max: 5 }, { min: -3, max: -1 }];
|
||||
expected = [{ min: -10, max: 5 }, { min: -3, max: 1.5 }];
|
||||
|
||||
alignYLevel(yaxes, alignY);
|
||||
expect(yaxes).toMatchObject(expected);
|
||||
});
|
||||
|
||||
it('Should shrink Y axes', () => {
|
||||
yaxes = [{ min: -3, max: -1 }, { min: -10, max: 5 }];
|
||||
expected = [{ min: -3, max: 1.5 }, { min: -10, max: 5 }];
|
||||
|
||||
alignYLevel(yaxes, alignY);
|
||||
expect(yaxes).toMatchObject(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('on level not zero', () => {
|
||||
it('Should shrink Y axis', () => {
|
||||
alignY = 1;
|
||||
yaxes = [{ min: 5, max: 10 }, { min: 2, max: 4 }];
|
||||
expected = [{ min: 4, max: 10 }, { min: 2, max: 4 }];
|
||||
|
||||
alignYLevel(yaxes, alignY);
|
||||
expect(yaxes).toMatchObject(expected);
|
||||
});
|
||||
|
||||
it('Should shrink Y axes', () => {
|
||||
alignY = 2;
|
||||
yaxes = [{ min: -3, max: 1 }, { min: 5, max: 10 }];
|
||||
expected = [{ min: -3, max: 7 }, { min: -6, max: 10 }];
|
||||
|
||||
alignYLevel(yaxes, alignY);
|
||||
expect(yaxes).toMatchObject(expected);
|
||||
});
|
||||
|
||||
it('Should shrink Y axes', () => {
|
||||
alignY = -1;
|
||||
yaxes = [{ min: -5, max: 5 }, { min: -2, max: 3 }];
|
||||
expected = [{ min: -5, max: 15 }, { min: -2, max: 3 }];
|
||||
|
||||
alignYLevel(yaxes, alignY);
|
||||
expect(yaxes).toMatchObject(expected);
|
||||
});
|
||||
|
||||
it('Should shrink Y axes', () => {
|
||||
alignY = -2;
|
||||
yaxes = [{ min: -2, max: 3 }, { min: 5, max: 10 }];
|
||||
expected = [{ min: -2, max: 3 }, { min: -2, max: 10 }];
|
||||
|
||||
alignYLevel(yaxes, alignY);
|
||||
expect(yaxes).toMatchObject(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('on level not number value', () => {
|
||||
it('Should ignore without errors', () => {
|
||||
yaxes = [{ min: 5, max: 10 }, { min: 2, max: 4 }];
|
||||
expected = [{ min: 5, max: 10 }, { min: 2, max: 4 }];
|
||||
|
||||
alignYLevel(yaxes, 'q');
|
||||
expect(yaxes).toMatchObject(expected);
|
||||
});
|
||||
});
|
||||
});
|
28
public/vendor/flot/jquery.flot.js
vendored
28
public/vendor/flot/jquery.flot.js
vendored
@ -632,6 +632,7 @@ Licensed under the MIT license.
|
||||
processRawData: [],
|
||||
processDatapoints: [],
|
||||
processOffset: [],
|
||||
processRange: [],
|
||||
drawBackground: [],
|
||||
drawSeries: [],
|
||||
draw: [],
|
||||
@ -1613,21 +1614,33 @@ Licensed under the MIT license.
|
||||
setRange(axis);
|
||||
});
|
||||
|
||||
executeHooks(hooks.processRange, []);
|
||||
|
||||
if (showGrid) {
|
||||
|
||||
var allocatedAxes = $.grep(axes, function (axis) {
|
||||
return axis.show || axis.reserveSpace;
|
||||
});
|
||||
|
||||
var snaped = false;
|
||||
for (var i = 0; i < 2; i++) {
|
||||
$.each(allocatedAxes, function (_, axis) {
|
||||
// make the ticks
|
||||
setupTickGeneration(axis);
|
||||
setTicks(axis);
|
||||
snapRangeToTicks(axis, axis.ticks);
|
||||
snaped = snapRangeToTicks(axis, axis.ticks) || snaped;
|
||||
// find labelWidth/Height for axis
|
||||
measureTickLabels(axis);
|
||||
});
|
||||
|
||||
if (snaped && hooks.processRange.length > 0) {
|
||||
executeHooks(hooks.processRange, []);
|
||||
snaped = false;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// with all dimensions calculated, we can compute the
|
||||
// axis bounding boxes, start from the outside
|
||||
// (reverse order)
|
||||
@ -1643,6 +1656,7 @@ Licensed under the MIT license.
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
plotWidth = surface.width - plotOffset.left - plotOffset.right;
|
||||
plotHeight = surface.height - plotOffset.bottom - plotOffset.top;
|
||||
|
||||
@ -1876,13 +1890,19 @@ Licensed under the MIT license.
|
||||
}
|
||||
|
||||
function snapRangeToTicks(axis, ticks) {
|
||||
var changed = false;
|
||||
if (axis.options.autoscaleMargin && ticks.length > 0) {
|
||||
// snap to ticks
|
||||
if (axis.options.min == null)
|
||||
if (axis.options.min == null) {
|
||||
axis.min = Math.min(axis.min, ticks[0].v);
|
||||
if (axis.options.max == null && ticks.length > 1)
|
||||
axis.max = Math.max(axis.max, ticks[ticks.length - 1].v);
|
||||
changed = true;
|
||||
}
|
||||
if (axis.options.max == null && ticks.length > 1) {
|
||||
axis.max = Math.max(axis.max, ticks[ticks.length - 1].v);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
function draw() {
|
||||
|
Loading…
Reference in New Issue
Block a user