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'; ...@@ -29,7 +29,7 @@ import { isNumber } from 'lodash';
const defaultFormatter = (v: any) => (v == null ? '-' : v.toFixed(1)); const defaultFormatter = (v: any) => (v == null ? '-' : v.toFixed(1));
export interface XYFieldMatchers { export interface XYFieldMatchers {
x: FieldMatcher; x: FieldMatcher; // first match
y: FieldMatcher; y: FieldMatcher;
} }
export interface GraphNGProps extends Omit<PlotProps, 'data' | 'config'> { export interface GraphNGProps extends Omit<PlotProps, 'data' | 'config'> {
......
import { ArrayVector, DataFrame, FieldType, toDataFrame } from '@grafana/data'; import { ArrayVector, DataFrame, FieldType, toDataFrame } from '@grafana/data';
import { AlignedFrameWithGapTest } from '../uPlot/types'; import { AlignedFrameWithGapTest } from '../uPlot/types';
import { alignDataFrames } from './utils'; import { alignDataFrames, isLikelyAscendingVector } from './utils';
describe('alignDataFrames', () => { describe('alignDataFrames', () => {
describe('aligned frame', () => { describe('aligned frame', () => {
...@@ -215,4 +215,31 @@ describe('alignDataFrames', () => { ...@@ -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 { ...@@ -9,6 +9,8 @@ import {
FieldType, FieldType,
FieldState, FieldState,
DataFrameFieldIndex, DataFrameFieldIndex,
sortDataFrame,
Vector,
} from '@grafana/data'; } from '@grafana/data';
import { AlignedFrameWithGapTest } from '../uPlot/types'; import { AlignedFrameWithGapTest } from '../uPlot/types';
import uPlot, { AlignedData, JoinNullMode } from 'uplot'; import uPlot, { AlignedData, JoinNullMode } from 'uplot';
...@@ -16,24 +18,23 @@ import { XYFieldMatchers } from './GraphNG'; ...@@ -16,24 +18,23 @@ import { XYFieldMatchers } from './GraphNG';
// the results ofter passing though data // the results ofter passing though data
export interface XYDimensionFields { export interface XYDimensionFields {
x: Field[]; x: Field; // independent axis (cause)
y: Field[]; y: Field[]; // dependent axis (effect)
} }
export function mapDimesions(match: XYFieldMatchers, frame: DataFrame, frames?: DataFrame[]): XYDimensionFields { export function mapDimesions(match: XYFieldMatchers, frame: DataFrame, frames?: DataFrame[]): XYDimensionFields {
const out: XYDimensionFields = { let x: Field | undefined;
x: [], const y: Field[] = [];
y: [],
};
for (const field of frame.fields) { for (const field of frame.fields) {
if (match.x(field, frame, frames ?? [])) { if (!x && match.x(field, frame, frames ?? [])) {
out.x.push(field); x = field;
} }
if (match.y(field, frame, frames ?? [])) { 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): ...@@ -58,26 +59,29 @@ export function alignDataFrames(frames: DataFrame[], fields?: XYFieldMatchers):
} }
for (let frameIndex = 0; frameIndex < frames.length; frameIndex++) { for (let frameIndex = 0; frameIndex < frames.length; frameIndex++) {
const frame = frames[frameIndex]; let frame = frames[frameIndex];
const dims = mapDimesions(fields, frame, frames); 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 continue; // no numeric and no time fields
} }
if (dims.x.length > 1) { // Quick check that x is ascending order
throw new Error('Only a single x field is supported'); 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]; let nullModesFrame: JoinNullMode[] = [0];
// Add the first X axis // Add the first X axis
if (!sourceFields.length) { if (!sourceFields.length) {
sourceFields.push(dims.x[0]); sourceFields.push(dims.x);
} }
const alignedData: AlignedData = [ 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++) { for (let fieldIndex = 0; fieldIndex < frame.fields.length; fieldIndex++) {
...@@ -150,3 +154,34 @@ export function alignDataFrames(frames: DataFrame[], fields?: XYFieldMatchers): ...@@ -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