Trend: Fix x-axis max affected by null-append to bar series (#110322)
This commit is contained in:
@@ -513,4 +513,288 @@ describe('GraphNG utils', () => {
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test('preparePlotFrame DOES NOT append min bar spaced nulls when all visible bar series have same min spacing', () => {
|
||||
const df1: DataFrame = {
|
||||
name: 'A',
|
||||
length: 5,
|
||||
fields: [
|
||||
{
|
||||
name: 'time',
|
||||
type: FieldType.time,
|
||||
config: {},
|
||||
values: [1, 2, 4, 6, 100], // should find smallest delta === 1 from here
|
||||
},
|
||||
{
|
||||
name: 'value',
|
||||
type: FieldType.number,
|
||||
config: {
|
||||
custom: {
|
||||
drawStyle: GraphDrawStyle.Bars,
|
||||
},
|
||||
},
|
||||
values: [1, 1, 1, 1, 1],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const df2: DataFrame = {
|
||||
name: 'B',
|
||||
length: 5,
|
||||
fields: [
|
||||
{
|
||||
name: 'time',
|
||||
type: FieldType.time,
|
||||
config: {},
|
||||
values: [30, 31, 50, 90, 100],
|
||||
},
|
||||
{
|
||||
name: 'value',
|
||||
type: FieldType.number,
|
||||
config: {
|
||||
custom: {
|
||||
drawStyle: GraphDrawStyle.Bars,
|
||||
},
|
||||
},
|
||||
values: [2, 2, 2, 2, 2],
|
||||
},
|
||||
{
|
||||
name: 'value',
|
||||
type: FieldType.number,
|
||||
config: {
|
||||
custom: {
|
||||
drawStyle: GraphDrawStyle.Line,
|
||||
},
|
||||
},
|
||||
values: [3, 3, 3, 3, 3],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const df3: DataFrame = {
|
||||
name: 'C',
|
||||
length: 2,
|
||||
fields: [
|
||||
{
|
||||
name: 'time',
|
||||
type: FieldType.time,
|
||||
config: {},
|
||||
values: [1, 1.1], // should not trip up on smaller deltas of non-bars
|
||||
},
|
||||
{
|
||||
name: 'value',
|
||||
type: FieldType.number,
|
||||
config: {
|
||||
custom: {
|
||||
drawStyle: GraphDrawStyle.Line,
|
||||
},
|
||||
},
|
||||
values: [4, 4],
|
||||
},
|
||||
{
|
||||
name: 'value',
|
||||
type: FieldType.number,
|
||||
config: {
|
||||
custom: {
|
||||
drawStyle: GraphDrawStyle.Bars,
|
||||
hideFrom: {
|
||||
viz: true, // should ignore hidden bar series
|
||||
},
|
||||
},
|
||||
},
|
||||
values: [4, 4],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
let aligndFrame = preparePlotFrame([df1, df2, df3], {
|
||||
x: fieldMatchers.get(FieldMatcherID.firstTimeField).get({}),
|
||||
y: fieldMatchers.get(FieldMatcherID.numeric).get({}),
|
||||
});
|
||||
|
||||
expect(aligndFrame).toMatchInlineSnapshot(`
|
||||
{
|
||||
"fields": [
|
||||
{
|
||||
"config": {},
|
||||
"name": "time",
|
||||
"state": {
|
||||
"nullThresholdApplied": true,
|
||||
"origin": {
|
||||
"fieldIndex": 0,
|
||||
"frameIndex": 0,
|
||||
},
|
||||
},
|
||||
"type": "time",
|
||||
"values": [
|
||||
1,
|
||||
1.1,
|
||||
2,
|
||||
4,
|
||||
6,
|
||||
30,
|
||||
31,
|
||||
50,
|
||||
90,
|
||||
100,
|
||||
],
|
||||
},
|
||||
{
|
||||
"config": {
|
||||
"custom": {
|
||||
"drawStyle": "bars",
|
||||
},
|
||||
},
|
||||
"labels": {
|
||||
"name": "A",
|
||||
},
|
||||
"name": "value",
|
||||
"state": {
|
||||
"origin": {
|
||||
"fieldIndex": 1,
|
||||
"frameIndex": 0,
|
||||
},
|
||||
},
|
||||
"type": "number",
|
||||
"values": [
|
||||
1,
|
||||
undefined,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
1,
|
||||
],
|
||||
},
|
||||
{
|
||||
"config": {
|
||||
"custom": {
|
||||
"drawStyle": "bars",
|
||||
},
|
||||
},
|
||||
"labels": {
|
||||
"name": "B",
|
||||
},
|
||||
"name": "value",
|
||||
"state": {
|
||||
"origin": {
|
||||
"fieldIndex": 1,
|
||||
"frameIndex": 1,
|
||||
},
|
||||
},
|
||||
"type": "number",
|
||||
"values": [
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
],
|
||||
},
|
||||
{
|
||||
"config": {
|
||||
"custom": {
|
||||
"drawStyle": "line",
|
||||
},
|
||||
},
|
||||
"labels": {
|
||||
"name": "B",
|
||||
},
|
||||
"name": "value",
|
||||
"state": {
|
||||
"origin": {
|
||||
"fieldIndex": 2,
|
||||
"frameIndex": 1,
|
||||
},
|
||||
},
|
||||
"type": "number",
|
||||
"values": [
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
3,
|
||||
3,
|
||||
3,
|
||||
3,
|
||||
3,
|
||||
],
|
||||
},
|
||||
{
|
||||
"config": {
|
||||
"custom": {
|
||||
"drawStyle": "line",
|
||||
},
|
||||
},
|
||||
"labels": {
|
||||
"name": "C",
|
||||
},
|
||||
"name": "value",
|
||||
"state": {
|
||||
"origin": {
|
||||
"fieldIndex": 1,
|
||||
"frameIndex": 2,
|
||||
},
|
||||
},
|
||||
"type": "number",
|
||||
"values": [
|
||||
4,
|
||||
4,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
],
|
||||
},
|
||||
{
|
||||
"config": {
|
||||
"custom": {
|
||||
"drawStyle": "bars",
|
||||
"hideFrom": {
|
||||
"viz": true,
|
||||
},
|
||||
},
|
||||
},
|
||||
"labels": {
|
||||
"name": "C",
|
||||
},
|
||||
"name": "value",
|
||||
"state": {
|
||||
"origin": {
|
||||
"fieldIndex": 2,
|
||||
"frameIndex": 2,
|
||||
},
|
||||
},
|
||||
"type": "number",
|
||||
"values": [
|
||||
4,
|
||||
4,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
],
|
||||
},
|
||||
],
|
||||
"length": 10,
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import { DataFrame, Field, FieldType, outerJoinDataFrames, TimeRange, applyNullInsertThreshold } from '@grafana/data';
|
||||
import {
|
||||
DataFrame,
|
||||
Field,
|
||||
FieldType,
|
||||
outerJoinDataFrames,
|
||||
TimeRange,
|
||||
applyNullInsertThreshold,
|
||||
roundDecimals,
|
||||
} from '@grafana/data';
|
||||
import { NULL_EXPAND, NULL_REMOVE, NULL_RETAIN, nullToUndefThreshold } from '@grafana/data/internal';
|
||||
import { GraphDrawStyle } from '@grafana/schema';
|
||||
|
||||
@@ -42,20 +50,22 @@ function applySpanNullsThresholds(frame: DataFrame, refFieldName?: string | null
|
||||
return frame;
|
||||
}
|
||||
|
||||
export function preparePlotFrame(frames: DataFrame[], dimFields: XYFieldMatchers, timeRange?: TimeRange | null) {
|
||||
let xField: Field;
|
||||
loop: for (let frame of frames) {
|
||||
for (let field of frame.fields) {
|
||||
if (dimFields.x(field, frame, frames)) {
|
||||
xField = field;
|
||||
break loop;
|
||||
}
|
||||
function getXField(dimFields: XYFieldMatchers, frame: DataFrame, frames: DataFrame[]) {
|
||||
for (let field of frame.fields) {
|
||||
if (dimFields.x(field, frame, frames)) {
|
||||
return field;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
export function preparePlotFrame(frames: DataFrame[], dimFields: XYFieldMatchers, timeRange?: TimeRange | null) {
|
||||
// apply null insertions at interval
|
||||
frames = frames.map((frame) => {
|
||||
if (!xField?.state?.nullThresholdApplied) {
|
||||
const xField = getXField(dimFields, frame, frames);
|
||||
|
||||
if (xField != null && !xField.state?.nullThresholdApplied) {
|
||||
return applyNullInsertThreshold({
|
||||
frame,
|
||||
refFieldName: xField.name,
|
||||
@@ -73,22 +83,43 @@ export function preparePlotFrame(frames: DataFrame[], dimFields: XYFieldMatchers
|
||||
);
|
||||
|
||||
// to make bar widths of all series uniform (equal to narrowest bar series), find smallest distance between x points
|
||||
let minXDelta = Infinity;
|
||||
let minXDeltaGlobal: number | null = null;
|
||||
|
||||
if (numBarSeries > 1) {
|
||||
// collect for each frame and only set minXDeltaGlobal if they're different
|
||||
const minXDeltas = new Set<number>();
|
||||
|
||||
frames.forEach((frame) => {
|
||||
if (!frame.fields.some(isVisibleBarField)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const xField = getXField(dimFields, frame, frames);
|
||||
|
||||
if (xField == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
let minXDeltaFrame = Infinity;
|
||||
|
||||
const xVals = xField.values;
|
||||
|
||||
for (let i = 0; i < xVals.length; i++) {
|
||||
if (i > 0) {
|
||||
minXDelta = Math.min(minXDelta, xVals[i] - xVals[i - 1]);
|
||||
minXDeltaFrame = Math.min(minXDeltaFrame, xVals[i] - xVals[i - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!Number.isInteger(minXDeltaFrame)) {
|
||||
minXDeltaFrame = roundDecimals(minXDeltaFrame, 6);
|
||||
}
|
||||
|
||||
minXDeltas.add(minXDeltaFrame);
|
||||
});
|
||||
|
||||
if (minXDeltas.size > 1) {
|
||||
minXDeltaGlobal = Math.min(...minXDeltas);
|
||||
}
|
||||
}
|
||||
|
||||
let alignedFrame = outerJoinDataFrames({
|
||||
@@ -116,16 +147,16 @@ export function preparePlotFrame(frames: DataFrame[], dimFields: XYFieldMatchers
|
||||
});
|
||||
|
||||
if (alignedFrame) {
|
||||
alignedFrame = applySpanNullsThresholds(alignedFrame, xField!.name);
|
||||
alignedFrame = applySpanNullsThresholds(alignedFrame, alignedFrame.fields[0].name);
|
||||
|
||||
// append 2 null vals at minXDelta to bar series
|
||||
if (minXDelta !== Infinity) {
|
||||
// append 2 null vals at minXDeltaGlobal to bar series
|
||||
if (minXDeltaGlobal != null) {
|
||||
alignedFrame.fields.forEach((f, fi) => {
|
||||
let vals = f.values;
|
||||
|
||||
if (fi === 0) {
|
||||
let lastVal = vals[vals.length - 1];
|
||||
vals.push(lastVal + minXDelta, lastVal + 2 * minXDelta);
|
||||
vals.push(lastVal + minXDeltaGlobal, lastVal + 2 * minXDeltaGlobal);
|
||||
} else if (isVisibleBarField(f)) {
|
||||
vals.push(null, null);
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user