Co-authored-by: Leon Sorokin <leeoniya@gmail.com>
(cherry picked from commit 4c8c2f6c96)
Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
This commit is contained in:
co-authored by
Ryan McKinley
parent
c6fb06721f
commit
e69544b6ea
@@ -115,7 +115,7 @@ export class GraphNG extends React.Component<GraphNGProps, GraphNGState> {
|
||||
|
||||
state = {
|
||||
alignedFrame,
|
||||
alignedData: config!.prepData!(alignedFrame),
|
||||
alignedData: config!.prepData!([alignedFrame]) as AlignedData,
|
||||
config,
|
||||
};
|
||||
|
||||
@@ -195,7 +195,7 @@ export class GraphNG extends React.Component<GraphNGProps, GraphNGState> {
|
||||
|
||||
if (shouldReconfig) {
|
||||
newState.config = this.props.prepConfig(newState.alignedFrame, this.props.frames, this.getTimeRange);
|
||||
newState.alignedData = newState.config.prepData!(newState.alignedFrame);
|
||||
newState.alignedData = newState.config.prepData!([newState.alignedFrame]) as AlignedData;
|
||||
pluginLog('GraphNG', false, 'config recreated', newState.config);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,6 +98,7 @@ Object {
|
||||
},
|
||||
},
|
||||
"hooks": Object {},
|
||||
"mode": 1,
|
||||
"padding": undefined,
|
||||
"scales": Object {
|
||||
"__fixed": Object {
|
||||
@@ -127,6 +128,7 @@ Object {
|
||||
1,
|
||||
2,
|
||||
],
|
||||
"facets": undefined,
|
||||
"fill": [Function],
|
||||
"paths": [Function],
|
||||
"points": Object {
|
||||
@@ -149,6 +151,7 @@ Object {
|
||||
1,
|
||||
2,
|
||||
],
|
||||
"facets": undefined,
|
||||
"fill": [Function],
|
||||
"paths": [Function],
|
||||
"points": Object {
|
||||
@@ -171,6 +174,7 @@ Object {
|
||||
1,
|
||||
2,
|
||||
],
|
||||
"facets": undefined,
|
||||
"fill": [Function],
|
||||
"paths": [Function],
|
||||
"points": Object {
|
||||
@@ -193,6 +197,7 @@ Object {
|
||||
1,
|
||||
2,
|
||||
],
|
||||
"facets": undefined,
|
||||
"fill": [Function],
|
||||
"paths": [Function],
|
||||
"points": Object {
|
||||
@@ -215,6 +220,7 @@ Object {
|
||||
1,
|
||||
2,
|
||||
],
|
||||
"facets": undefined,
|
||||
"fill": [Function],
|
||||
"paths": [Function],
|
||||
"points": Object {
|
||||
|
||||
@@ -50,7 +50,7 @@ export class Sparkline extends PureComponent<SparklineProps, State> {
|
||||
const alignedDataFrame = preparePlotFrame(props.sparkline, props.config);
|
||||
|
||||
this.state = {
|
||||
data: preparePlotData(alignedDataFrame),
|
||||
data: preparePlotData([alignedDataFrame]),
|
||||
alignedDataFrame,
|
||||
configBuilder: this.prepareConfig(alignedDataFrame),
|
||||
};
|
||||
@@ -64,7 +64,7 @@ export class Sparkline extends PureComponent<SparklineProps, State> {
|
||||
|
||||
return {
|
||||
...state,
|
||||
data: preparePlotData(frame),
|
||||
data: preparePlotData([frame]),
|
||||
alignedDataFrame: frame,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -246,12 +246,14 @@ export { LegacyForms, LegacyInputStatus };
|
||||
|
||||
// WIP, need renames and exports cleanup
|
||||
export * from './uPlot/config';
|
||||
export { UPlotConfigBuilder, UPlotConfigPrepFn } from './uPlot/config/UPlotConfigBuilder';
|
||||
export { ScaleDistribution } from '@grafana/schema';
|
||||
export { UPlotConfigBuilder } from './uPlot/config/UPlotConfigBuilder';
|
||||
export { UPlotChart } from './uPlot/Plot';
|
||||
export { PlotLegend } from './uPlot/PlotLegend';
|
||||
export * from './uPlot/geometries';
|
||||
export * from './uPlot/plugins';
|
||||
export { PlotTooltipInterpolator, PlotSelection } from './uPlot/types';
|
||||
export { UPlotConfigPrepFn } from './uPlot/config/UPlotConfigBuilder';
|
||||
export { GraphNG, GraphNGProps, FIXED_UNIT } from './GraphNG/GraphNG';
|
||||
export { TimeSeries } from './TimeSeries/TimeSeries';
|
||||
export { useGraphNGContext } from './GraphNG/hooks';
|
||||
|
||||
@@ -55,7 +55,7 @@ const mockData = () => {
|
||||
|
||||
const config = new UPlotConfigBuilder();
|
||||
config.addSeries({} as SeriesProps);
|
||||
return { data, timeRange, config };
|
||||
return { data: [data], timeRange, config };
|
||||
};
|
||||
|
||||
describe('UPlotChart', () => {
|
||||
@@ -104,7 +104,7 @@ describe('UPlotChart', () => {
|
||||
|
||||
expect(uPlot).toBeCalledTimes(1);
|
||||
|
||||
data.fields[1].values.set(0, 1);
|
||||
data[0].fields[1].values.set(0, 1);
|
||||
|
||||
rerender(
|
||||
<UPlotChart
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { createRef } from 'react';
|
||||
import uPlot, { Options } from 'uplot';
|
||||
import uPlot, { AlignedData, Options } from 'uplot';
|
||||
import { DEFAULT_PLOT_CONFIG, pluginLog } from './utils';
|
||||
import { PlotProps } from './types';
|
||||
|
||||
@@ -72,7 +72,7 @@ export class UPlotChart extends React.Component<PlotProps, UPlotChartState> {
|
||||
};
|
||||
|
||||
pluginLog('UPlot', false, 'Reinitializing plot', config);
|
||||
const plot = new uPlot(config, this.props.data, this.plotContainer!.current!);
|
||||
const plot = new uPlot(config, this.props.data as AlignedData, this.plotContainer!.current!);
|
||||
|
||||
if (plotRef) {
|
||||
plotRef(plot);
|
||||
@@ -100,12 +100,12 @@ export class UPlotChart extends React.Component<PlotProps, UPlotChartState> {
|
||||
} else if (!sameConfig(prevProps, this.props)) {
|
||||
this.reinitPlot();
|
||||
} else if (!sameData(prevProps, this.props)) {
|
||||
plot?.setData(this.props.data);
|
||||
plot?.setData(this.props.data as AlignedData);
|
||||
|
||||
// this is a uPlot cache-busting hack for bar charts in case x axis labels changed
|
||||
// since the x scale's "range" doesnt change, the axis size doesnt get recomputed, which is where the tick labels are regenerated & cached
|
||||
// the more expensive, more proper/thorough way to do this is to force all axes to recalc: plot?.redraw(false, true);
|
||||
if (plot && typeof this.props.data[0][0] === 'string') {
|
||||
if (plot && typeof this.props.data[0]?.[0] === 'string') {
|
||||
//@ts-ignore
|
||||
plot.axes[0]._values = this.props.data[0];
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ export interface AxisProps {
|
||||
formatValue?: (v: any) => string;
|
||||
incrs?: Axis.Incrs;
|
||||
splits?: Axis.Splits;
|
||||
values?: any;
|
||||
values?: Axis.Values;
|
||||
isTime?: boolean;
|
||||
timeZone?: TimeZone;
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ describe('UPlotConfigBuilder', () => {
|
||||
},
|
||||
},
|
||||
"hooks": Object {},
|
||||
"mode": 1,
|
||||
"padding": undefined,
|
||||
"scales": Object {},
|
||||
"select": undefined,
|
||||
@@ -87,6 +88,7 @@ describe('UPlotConfigBuilder', () => {
|
||||
},
|
||||
},
|
||||
"hooks": Object {},
|
||||
"mode": 1,
|
||||
"padding": undefined,
|
||||
"scales": Object {
|
||||
"scale-x": Object {
|
||||
@@ -166,6 +168,7 @@ describe('UPlotConfigBuilder', () => {
|
||||
},
|
||||
},
|
||||
"hooks": Object {},
|
||||
"mode": 1,
|
||||
"padding": undefined,
|
||||
"scales": Object {
|
||||
"scale-y": Object {
|
||||
@@ -218,6 +221,7 @@ describe('UPlotConfigBuilder', () => {
|
||||
},
|
||||
},
|
||||
"hooks": Object {},
|
||||
"mode": 1,
|
||||
"padding": undefined,
|
||||
"scales": Object {
|
||||
"scale-y": Object {
|
||||
@@ -271,6 +275,7 @@ describe('UPlotConfigBuilder', () => {
|
||||
},
|
||||
},
|
||||
"hooks": Object {},
|
||||
"mode": 1,
|
||||
"padding": undefined,
|
||||
"scales": Object {
|
||||
"scale-y": Object {
|
||||
@@ -387,6 +392,7 @@ describe('UPlotConfigBuilder', () => {
|
||||
},
|
||||
},
|
||||
"hooks": Object {},
|
||||
"mode": 1,
|
||||
"padding": undefined,
|
||||
"scales": Object {},
|
||||
"select": undefined,
|
||||
@@ -505,6 +511,7 @@ describe('UPlotConfigBuilder', () => {
|
||||
},
|
||||
},
|
||||
"hooks": Object {},
|
||||
"mode": 1,
|
||||
"padding": undefined,
|
||||
"scales": Object {},
|
||||
"select": undefined,
|
||||
@@ -513,6 +520,7 @@ describe('UPlotConfigBuilder', () => {
|
||||
"value": [Function],
|
||||
},
|
||||
Object {
|
||||
"facets": undefined,
|
||||
"fill": [Function],
|
||||
"paths": [Function],
|
||||
"points": Object {
|
||||
@@ -619,6 +627,7 @@ describe('UPlotConfigBuilder', () => {
|
||||
},
|
||||
},
|
||||
"hooks": Object {},
|
||||
"mode": 1,
|
||||
"padding": undefined,
|
||||
"scales": Object {},
|
||||
"select": undefined,
|
||||
@@ -627,6 +636,7 @@ describe('UPlotConfigBuilder', () => {
|
||||
"value": [Function],
|
||||
},
|
||||
Object {
|
||||
"facets": undefined,
|
||||
"fill": [Function],
|
||||
"paths": [Function],
|
||||
"points": Object {
|
||||
@@ -644,6 +654,7 @@ describe('UPlotConfigBuilder', () => {
|
||||
"width": 1,
|
||||
},
|
||||
Object {
|
||||
"facets": undefined,
|
||||
"fill": [Function],
|
||||
"paths": [Function],
|
||||
"points": Object {
|
||||
@@ -661,6 +672,7 @@ describe('UPlotConfigBuilder', () => {
|
||||
"width": 1,
|
||||
},
|
||||
Object {
|
||||
"facets": undefined,
|
||||
"fill": [Function],
|
||||
"paths": [Function],
|
||||
"points": Object {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import uPlot, { Cursor, Band, Hooks, Select, AlignedData, Padding } from 'uplot';
|
||||
import uPlot, { Cursor, Band, Hooks, Select, AlignedData, Padding, Series } from 'uplot';
|
||||
import { merge } from 'lodash';
|
||||
import {
|
||||
DataFrame,
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
TimeRange,
|
||||
TimeZone,
|
||||
} from '@grafana/data';
|
||||
import { PlotConfig, PlotTooltipInterpolator } from '../types';
|
||||
import { FacetedData, PlotConfig, PlotTooltipInterpolator } from '../types';
|
||||
import { ScaleProps, UPlotScaleBuilder } from './UPlotScaleBuilder';
|
||||
import { SeriesProps, UPlotSeriesBuilder } from './UPlotSeriesBuilder';
|
||||
import { AxisProps, UPlotAxisBuilder } from './UPlotAxisBuilder';
|
||||
@@ -31,7 +31,7 @@ const cursorDefaults: Cursor = {
|
||||
},
|
||||
};
|
||||
|
||||
type PrepData = (frame: DataFrame) => AlignedData;
|
||||
type PrepData = (frames: DataFrame[]) => AlignedData | FacetedData;
|
||||
|
||||
export class UPlotConfigBuilder {
|
||||
private series: UPlotSeriesBuilder[] = [];
|
||||
@@ -45,7 +45,8 @@ export class UPlotConfigBuilder {
|
||||
private hooks: Hooks.Arrays = {};
|
||||
private tz: string | undefined = undefined;
|
||||
private sync = false;
|
||||
private frame: DataFrame | undefined = undefined;
|
||||
private mode: uPlot.Mode = 1;
|
||||
private frames: DataFrame[] | undefined = undefined;
|
||||
// to prevent more than one threshold per scale
|
||||
private thresholds: Record<string, UPlotThresholdOptions> = {};
|
||||
// Custom handler for closest datapoint and series lookup
|
||||
@@ -112,6 +113,10 @@ export class UPlotConfigBuilder {
|
||||
this.cursor = merge({}, this.cursor, cursor);
|
||||
}
|
||||
|
||||
setMode(mode: uPlot.Mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
setSelect(select: Select) {
|
||||
this.select = select;
|
||||
}
|
||||
@@ -151,9 +156,9 @@ export class UPlotConfigBuilder {
|
||||
}
|
||||
|
||||
setPrepData(prepData: PrepData) {
|
||||
this.prepData = (frame) => {
|
||||
this.frame = frame;
|
||||
return prepData(frame);
|
||||
this.prepData = (frames) => {
|
||||
this.frames = frames;
|
||||
return prepData(frames);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -171,10 +176,13 @@ export class UPlotConfigBuilder {
|
||||
|
||||
getConfig() {
|
||||
const config: PlotConfig = {
|
||||
mode: this.mode,
|
||||
series: [
|
||||
{
|
||||
value: () => '',
|
||||
},
|
||||
this.mode === 2
|
||||
? ((null as unknown) as Series)
|
||||
: {
|
||||
value: () => '',
|
||||
},
|
||||
],
|
||||
};
|
||||
config.axes = this.ensureNonOverlappingAxes(Object.values(this.axes)).map((a) => a.getConfig());
|
||||
@@ -193,19 +201,24 @@ export class UPlotConfigBuilder {
|
||||
|
||||
// interpolate for gradients/thresholds
|
||||
if (typeof s !== 'string') {
|
||||
let field = this.frame!.fields[seriesIdx];
|
||||
let field = this.frames![0].fields[seriesIdx];
|
||||
s = field.display!(field.values.get(u.cursor.idxs![seriesIdx]!)).color!;
|
||||
}
|
||||
|
||||
return s + alphaHex;
|
||||
};
|
||||
|
||||
config.cursor = merge({}, cursorDefaults, this.cursor, {
|
||||
points: {
|
||||
stroke: pointColorFn('80'),
|
||||
fill: pointColorFn(),
|
||||
config.cursor = merge(
|
||||
{},
|
||||
cursorDefaults,
|
||||
{
|
||||
points: {
|
||||
stroke: pointColorFn('80'),
|
||||
fill: pointColorFn(),
|
||||
},
|
||||
},
|
||||
});
|
||||
this.cursor
|
||||
);
|
||||
|
||||
config.tzDate = this.tzDate;
|
||||
config.padding = this.padding;
|
||||
|
||||
@@ -27,6 +27,8 @@ export interface SeriesProps extends LineConfig, BarConfig, FillConfig, PointsCo
|
||||
pxAlign?: boolean;
|
||||
gradientMode?: GraphGradientMode;
|
||||
|
||||
facets?: uPlot.Series.Facet[];
|
||||
|
||||
/** Used when gradientMode is set to Scheme */
|
||||
thresholds?: ThresholdsConfig;
|
||||
colorMode?: FieldColorMode;
|
||||
@@ -48,6 +50,7 @@ export interface SeriesProps extends LineConfig, BarConfig, FillConfig, PointsCo
|
||||
export class UPlotSeriesBuilder extends PlotConfigBuilder<SeriesProps, Series> {
|
||||
getConfig() {
|
||||
const {
|
||||
facets,
|
||||
drawStyle,
|
||||
pathBuilder,
|
||||
pointsBuilder,
|
||||
@@ -132,6 +135,7 @@ export class UPlotSeriesBuilder extends PlotConfigBuilder<SeriesProps, Series> {
|
||||
|
||||
return {
|
||||
scale: scaleKey,
|
||||
facets,
|
||||
spanGaps: typeof spanNulls === 'number' ? false : spanNulls,
|
||||
value: () => '',
|
||||
pxAlign,
|
||||
|
||||
@@ -5,15 +5,19 @@ import { UPlotConfigBuilder } from './config/UPlotConfigBuilder';
|
||||
|
||||
export type PlotConfig = Pick<
|
||||
Options,
|
||||
'series' | 'scales' | 'axes' | 'cursor' | 'bands' | 'hooks' | 'select' | 'tzDate' | 'padding'
|
||||
'mode' | 'series' | 'scales' | 'axes' | 'cursor' | 'bands' | 'hooks' | 'select' | 'tzDate' | 'padding'
|
||||
>;
|
||||
|
||||
export interface PlotPluginProps {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export type FacetValues = any[];
|
||||
export type FacetSeries = FacetValues[];
|
||||
export type FacetedData = [_: null, ...series: FacetSeries];
|
||||
|
||||
export interface PlotProps {
|
||||
data: AlignedData;
|
||||
data: AlignedData | FacetedData;
|
||||
width: number;
|
||||
height: number;
|
||||
config: UPlotConfigBuilder;
|
||||
|
||||
@@ -27,7 +27,7 @@ describe('preparePlotData', () => {
|
||||
});
|
||||
|
||||
it('creates array from DataFrame', () => {
|
||||
expect(preparePlotData(df)).toMatchInlineSnapshot(`
|
||||
expect(preparePlotData([df])).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
9997,
|
||||
@@ -75,7 +75,7 @@ describe('preparePlotData', () => {
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(preparePlotData(df)).toMatchInlineSnapshot(`
|
||||
expect(preparePlotData([df])).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
9997,
|
||||
@@ -122,7 +122,7 @@ describe('preparePlotData', () => {
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(preparePlotData(df)).toMatchInlineSnapshot(`
|
||||
expect(preparePlotData([df])).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
9997,
|
||||
@@ -185,7 +185,7 @@ describe('preparePlotData', () => {
|
||||
],
|
||||
});
|
||||
|
||||
expect(preparePlotData(df)).toMatchInlineSnapshot(`
|
||||
expect(preparePlotData([df])).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
9997,
|
||||
@@ -260,7 +260,7 @@ describe('preparePlotData', () => {
|
||||
],
|
||||
});
|
||||
|
||||
expect(preparePlotData(df)).toMatchInlineSnapshot(`
|
||||
expect(preparePlotData([df])).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
9997,
|
||||
|
||||
@@ -39,7 +39,8 @@ interface StackMeta {
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function preparePlotData(frame: DataFrame, onStackMeta?: (meta: StackMeta) => void): AlignedData {
|
||||
export function preparePlotData(frames: DataFrame[], onStackMeta?: (meta: StackMeta) => void): AlignedData {
|
||||
const frame = frames[0];
|
||||
const result: any[] = [];
|
||||
const stackingGroups: Map<string, number[]> = new Map();
|
||||
let seriesIndex = 0;
|
||||
|
||||
Reference in New Issue
Block a user