Commit f27450ed by Ryan McKinley Committed by GitHub

GraphNG: sort ascending if the values appear reversed (#30405)

parent e9e16cee
......@@ -29,7 +29,7 @@ import { isNumber } from 'lodash';
const defaultFormatter = (v: any) => (v == null ? '-' : v.toFixed(1));
export interface XYFieldMatchers {
x: FieldMatcher;
x: FieldMatcher; // first match
y: FieldMatcher;
}
export interface GraphNGProps extends Omit<PlotProps, 'data' | 'config'> {
......
import { ArrayVector, DataFrame, FieldType, toDataFrame } from '@grafana/data';
import { AlignedFrameWithGapTest } from '../uPlot/types';
import { alignDataFrames } from './utils';
import { alignDataFrames, isLikelyAscendingVector } from './utils';
describe('alignDataFrames', () => {
describe('aligned frame', () => {
......@@ -215,4 +215,31 @@ describe('alignDataFrames', () => {
});
});
});
describe('check ascending data', () => {
it('simple ascending', () => {
const v = new ArrayVector([1, 2, 3, 4, 5]);
expect(isLikelyAscendingVector(v)).toBeTruthy();
});
it('simple ascending with null', () => {
const v = new ArrayVector([null, 2, 3, 4, null]);
expect(isLikelyAscendingVector(v)).toBeTruthy();
});
it('single value', () => {
const v = new ArrayVector([null, null, null, 4, null]);
expect(isLikelyAscendingVector(v)).toBeTruthy();
expect(isLikelyAscendingVector(new ArrayVector([4]))).toBeTruthy();
expect(isLikelyAscendingVector(new ArrayVector([]))).toBeTruthy();
});
it('middle values', () => {
const v = new ArrayVector([null, null, 5, 4, null]);
expect(isLikelyAscendingVector(v)).toBeFalsy();
});
it('decending', () => {
expect(isLikelyAscendingVector(new ArrayVector([7, 6, null]))).toBeFalsy();
expect(isLikelyAscendingVector(new ArrayVector([7, 8, 6]))).toBeFalsy();
});
});
});
......@@ -9,6 +9,8 @@ import {
FieldType,
FieldState,
DataFrameFieldIndex,
sortDataFrame,
Vector,
} from '@grafana/data';
import { AlignedFrameWithGapTest } from '../uPlot/types';
import uPlot, { AlignedData, JoinNullMode } from 'uplot';
......@@ -16,24 +18,23 @@ import { XYFieldMatchers } from './GraphNG';
// the results ofter passing though data
export interface XYDimensionFields {
x: Field[];
y: Field[];
x: Field; // independent axis (cause)
y: Field[]; // dependent axis (effect)
}
export function mapDimesions(match: XYFieldMatchers, frame: DataFrame, frames?: DataFrame[]): XYDimensionFields {
const out: XYDimensionFields = {
x: [],
y: [],
};
let x: Field | undefined;
const y: Field[] = [];
for (const field of frame.fields) {
if (match.x(field, frame, frames ?? [])) {
out.x.push(field);
if (!x && match.x(field, frame, frames ?? [])) {
x = field;
}
if (match.y(field, frame, frames ?? [])) {
out.y.push(field);
y.push(field);
}
}
return out;
return { x: x as Field, y };
}
/**
......@@ -58,26 +59,29 @@ export function alignDataFrames(frames: DataFrame[], fields?: XYFieldMatchers):
}
for (let frameIndex = 0; frameIndex < frames.length; frameIndex++) {
const frame = frames[frameIndex];
const dims = mapDimesions(fields, frame, frames);
let frame = frames[frameIndex];
let dims = mapDimesions(fields, frame, frames);
if (!(dims.x.length && dims.y.length)) {
if (!(dims.x && dims.y.length)) {
continue; // no numeric and no time fields
}
if (dims.x.length > 1) {
throw new Error('Only a single x field is supported');
// Quick check that x is ascending order
if (!isLikelyAscendingVector(dims.x.values)) {
const xIndex = frame.fields.indexOf(dims.x);
frame = sortDataFrame(frame, xIndex);
dims = mapDimesions(fields, frame, frames);
}
let nullModesFrame: JoinNullMode[] = [0];
// Add the first X axis
if (!sourceFields.length) {
sourceFields.push(dims.x[0]);
sourceFields.push(dims.x);
}
const alignedData: AlignedData = [
dims.x[0].values.toArray(), // The x axis (time)
dims.x.values.toArray(), // The x axis (time)
];
for (let fieldIndex = 0; fieldIndex < frame.fields.length; fieldIndex++) {
......@@ -150,3 +154,34 @@ export function alignDataFrames(frames: DataFrame[], fields?: XYFieldMatchers):
},
};
}
// Quick test if the first and last points look to be ascending
export function isLikelyAscendingVector(data: Vector): boolean {
let first: any = undefined;
for (let idx = 0; idx < data.length; idx++) {
const v = data.get(idx);
if (v != null) {
if (first != null) {
if (first > v) {
return false; // descending
}
break;
}
first = v;
}
}
let idx = data.length - 1;
while (idx >= 0) {
const v = data.get(idx--);
if (v != null) {
if (first > v) {
return false;
}
return true;
}
}
return true; // only one non-null point
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment