Merge pull request #10994 from ilgizar/1271_share_zero

pull/11352/head
Marcus Efraimsson 7 years ago
commit 1ee0d1c296
  1. 154
      public/app/plugins/panel/graph/align_yaxes.ts
  2. 19
      public/app/plugins/panel/graph/axes_editor.html
  3. 12
      public/app/plugins/panel/graph/graph.ts
  4. 4
      public/app/plugins/panel/graph/module.ts
  5. 210
      public/app/plugins/panel/graph/specs/align_yaxes.jest.ts
  6. 40
      public/vendor/flot/jquery.flot.js

@ -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>
@ -28,8 +29,10 @@
<label class="gf-form-label width-6">Y-Max</label>
<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 class="gf-form">
</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>
</div>
@ -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

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

@ -632,6 +632,7 @@ Licensed under the MIT license.
processRawData: [],
processDatapoints: [],
processOffset: [],
processRange: [],
drawBackground: [],
drawSeries: [],
draw: [],
@ -1613,20 +1614,32 @@ Licensed under the MIT license.
setRange(axis);
});
executeHooks(hooks.processRange, []);
if (showGrid) {
var allocatedAxes = $.grep(axes, function (axis) {
return axis.show || axis.reserveSpace;
});
$.each(allocatedAxes, function (_, axis) {
// make the ticks
setupTickGeneration(axis);
setTicks(axis);
snapRangeToTicks(axis, axis.ticks);
// find labelWidth/Height for axis
measureTickLabels(axis);
});
var snaped = false;
for (var i = 0; i < 2; i++) {
$.each(allocatedAxes, function (_, axis) {
// make the ticks
setupTickGeneration(axis);
setTicks(axis);
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
@ -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)
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…
Cancel
Save