Commit f2327baf by Ryan McKinley Committed by GitHub

DataFrame: cache frame/field index in field state (#30529)

parent f97348ff
...@@ -162,6 +162,13 @@ export interface FieldState { ...@@ -162,6 +162,13 @@ export interface FieldState {
* Useful for assigning color to series by looking up a color in a palette using this index * Useful for assigning color to series by looking up a color in a palette using this index
*/ */
seriesIndex?: number; seriesIndex?: number;
/**
* Location of this field within the context frames results
*
* @internal -- we will try to make this unnecessary
*/
origin?: DataFrameFieldIndex;
} }
export interface NumericRange { export interface NumericRange {
...@@ -206,7 +213,8 @@ export const TIME_SERIES_METRIC_FIELD_NAME = 'Metric'; ...@@ -206,7 +213,8 @@ export const TIME_SERIES_METRIC_FIELD_NAME = 'Metric';
/** /**
* Describes where a specific data frame field is located within a * Describes where a specific data frame field is located within a
* dataset of type DataFrame[] * dataset of type DataFrame[]
* @public *
* @internal -- we will try to make this unnecessary
*/ */
export interface DataFrameFieldIndex { export interface DataFrameFieldIndex {
frameIndex: number; frameIndex: number;
......
...@@ -12,7 +12,7 @@ import { ...@@ -12,7 +12,7 @@ import {
reduceField, reduceField,
TimeRange, TimeRange,
} from '@grafana/data'; } from '@grafana/data';
import { alignDataFrames } from './utils'; import { joinDataFrames } from './utils';
import { useTheme } from '../../themes'; import { useTheme } from '../../themes';
import { UPlotChart } from '../uPlot/Plot'; import { UPlotChart } from '../uPlot/Plot';
import { PlotProps } from '../uPlot/types'; import { PlotProps } from '../uPlot/types';
...@@ -64,9 +64,7 @@ export const GraphNG: React.FC<GraphNGProps> = ({ ...@@ -64,9 +64,7 @@ export const GraphNG: React.FC<GraphNGProps> = ({
const theme = useTheme(); const theme = useTheme();
const hasLegend = useRef(legend && legend.displayMode !== LegendDisplayMode.Hidden); const hasLegend = useRef(legend && legend.displayMode !== LegendDisplayMode.Hidden);
const alignedFrameWithGapTest = useMemo(() => alignDataFrames(data, fields), [data, fields]); const frame = useMemo(() => joinDataFrames(data, fields), [data, fields]);
const alignedFrame = alignedFrameWithGapTest?.frame;
const getDataFrameFieldIndex = alignedFrameWithGapTest?.getDataFrameFieldIndex;
const compareFrames = useCallback((a?: DataFrame | null, b?: DataFrame | null) => { const compareFrames = useCallback((a?: DataFrame | null, b?: DataFrame | null) => {
if (a && b) { if (a && b) {
...@@ -98,17 +96,17 @@ export const GraphNG: React.FC<GraphNGProps> = ({ ...@@ -98,17 +96,17 @@ export const GraphNG: React.FC<GraphNGProps> = ({
currentTimeRange.current = timeRange; currentTimeRange.current = timeRange;
}, [timeRange]); }, [timeRange]);
const configRev = useRevision(alignedFrame, compareFrames); const configRev = useRevision(frame, compareFrames);
const configBuilder = useMemo(() => { const configBuilder = useMemo(() => {
const builder = new UPlotConfigBuilder(); const builder = new UPlotConfigBuilder();
if (!alignedFrame) { if (!frame) {
return builder; return builder;
} }
// X is the first field in the aligned frame // X is the first field in the aligned frame
const xField = alignedFrame.fields[0]; const xField = frame.fields[0];
if (xField.type === FieldType.time) { if (xField.type === FieldType.time) {
builder.addScale({ builder.addScale({
...@@ -141,8 +139,8 @@ export const GraphNG: React.FC<GraphNGProps> = ({ ...@@ -141,8 +139,8 @@ export const GraphNG: React.FC<GraphNGProps> = ({
} }
let indexByName: Map<string, number> | undefined = undefined; let indexByName: Map<string, number> | undefined = undefined;
for (let i = 0; i < alignedFrame.fields.length; i++) { for (let i = 0; i < frame.fields.length; i++) {
const field = alignedFrame.fields[i]; const field = frame.fields[i];
const config = field.config as FieldConfig<GraphFieldConfig>; const config = field.config as FieldConfig<GraphFieldConfig>;
const customConfig: GraphFieldConfig = { const customConfig: GraphFieldConfig = {
...defaultConfig, ...defaultConfig,
...@@ -182,14 +180,13 @@ export const GraphNG: React.FC<GraphNGProps> = ({ ...@@ -182,14 +180,13 @@ export const GraphNG: React.FC<GraphNGProps> = ({
} }
const showPoints = customConfig.drawStyle === DrawStyle.Points ? PointVisibility.Always : customConfig.showPoints; const showPoints = customConfig.drawStyle === DrawStyle.Points ? PointVisibility.Always : customConfig.showPoints;
const dataFrameFieldIndex = getDataFrameFieldIndex ? getDataFrameFieldIndex(i) : undefined;
let { fillOpacity } = customConfig; let { fillOpacity } = customConfig;
if (customConfig.fillBelowTo) { if (customConfig.fillBelowTo) {
if (!indexByName) { if (!indexByName) {
indexByName = getNamesToFieldIndex(alignedFrame); indexByName = getNamesToFieldIndex(frame);
} }
const t = indexByName.get(getFieldDisplayName(field, alignedFrame)); const t = indexByName.get(getFieldDisplayName(field, frame));
const b = indexByName.get(customConfig.fillBelowTo); const b = indexByName.get(customConfig.fillBelowTo);
if (isNumber(b) && isNumber(t)) { if (isNumber(b) && isNumber(t)) {
builder.addBand({ builder.addBand({
...@@ -221,15 +218,15 @@ export const GraphNG: React.FC<GraphNGProps> = ({ ...@@ -221,15 +218,15 @@ export const GraphNG: React.FC<GraphNGProps> = ({
thresholds: config.thresholds, thresholds: config.thresholds,
// The following properties are not used in the uPlot config, but are utilized as transport for legend config // The following properties are not used in the uPlot config, but are utilized as transport for legend config
dataFrameFieldIndex, dataFrameFieldIndex: field.state?.origin,
fieldName: getFieldDisplayName(field, alignedFrame), fieldName: getFieldDisplayName(field, frame),
hideInLegend: customConfig.hideFrom?.legend, hideInLegend: customConfig.hideFrom?.legend,
}); });
} }
return builder; return builder;
}, [configRev, timeZone]); }, [configRev, timeZone]);
if (alignedFrameWithGapTest == null) { if (!frame) {
return ( return (
<div className="panel-empty"> <div className="panel-empty">
<p>No data found in response</p> <p>No data found in response</p>
...@@ -299,7 +296,7 @@ export const GraphNG: React.FC<GraphNGProps> = ({ ...@@ -299,7 +296,7 @@ export const GraphNG: React.FC<GraphNGProps> = ({
<VizLayout width={width} height={height} legend={legendElement}> <VizLayout width={width} height={height} legend={legendElement}>
{(vizWidth: number, vizHeight: number) => ( {(vizWidth: number, vizHeight: number) => (
<UPlotChart <UPlotChart
data={alignedFrameWithGapTest} data={frame}
config={configBuilder} config={configBuilder}
width={vizWidth} width={vizWidth}
height={vizHeight} height={vizHeight}
......
import { ArrayVector, DataFrame, FieldType, toDataFrame } from '@grafana/data'; import { ArrayVector, DataFrame, FieldType, toDataFrame } from '@grafana/data';
import { AlignedFrameWithGapTest } from '../uPlot/types'; import { joinDataFrames, isLikelyAscendingVector } from './utils';
import { alignDataFrames, isLikelyAscendingVector } from './utils';
describe('alignDataFrames', () => { describe('joinDataFrames', () => {
describe('aligned frame', () => { describe('joined frame', () => {
it('should align multiple data frames into one data frame', () => { it('should align multiple data frames into one data frame', () => {
const data: DataFrame[] = [ const data: DataFrame[] = [
toDataFrame({ toDataFrame({
...@@ -20,37 +19,64 @@ describe('alignDataFrames', () => { ...@@ -20,37 +19,64 @@ describe('alignDataFrames', () => {
}), }),
]; ];
const aligned = alignDataFrames(data); const joined = joinDataFrames(data);
expect(aligned?.frame.fields).toEqual([ expect(joined?.fields).toMatchInlineSnapshot(`
{ Array [
config: {}, Object {
state: {}, "config": Object {},
name: 'time', "name": "time",
type: FieldType.time, "state": Object {
values: new ArrayVector([1000, 2000, 3000, 4000]), "origin": undefined,
}, },
{ "type": "time",
config: {}, "values": Array [
state: { 1000,
displayName: 'temperature A', 2000,
seriesIndex: 0, 3000,
4000,
],
},
Object {
"config": Object {},
"name": "temperature A",
"state": Object {
"displayName": "temperature A",
"origin": Object {
"fieldIndex": 1,
"frameIndex": 0,
},
"seriesIndex": 0,
},
"type": "number",
"values": Array [
1,
3,
5,
7,
],
}, },
name: 'temperature A', Object {
type: FieldType.number, "config": Object {},
values: new ArrayVector([1, 3, 5, 7]), "name": "temperature B",
}, "state": Object {
{ "displayName": "temperature B",
config: {}, "origin": Object {
state: { "fieldIndex": 1,
displayName: 'temperature B', "frameIndex": 1,
seriesIndex: 1, },
"seriesIndex": 1,
},
"type": "number",
"values": Array [
0,
2,
6,
7,
],
}, },
name: 'temperature B', ]
type: FieldType.number, `);
values: new ArrayVector([0, 2, 6, 7]),
},
]);
}); });
it('should align multiple data frames into one data frame but only keep first time field', () => { it('should align multiple data frames into one data frame but only keep first time field', () => {
...@@ -69,37 +95,64 @@ describe('alignDataFrames', () => { ...@@ -69,37 +95,64 @@ describe('alignDataFrames', () => {
}), }),
]; ];
const aligned = alignDataFrames(data); const aligned = joinDataFrames(data);
expect(aligned?.frame.fields).toEqual([ expect(aligned?.fields).toMatchInlineSnapshot(`
{ Array [
config: {}, Object {
state: {}, "config": Object {},
name: 'time', "name": "time",
type: FieldType.time, "state": Object {
values: new ArrayVector([1000, 2000, 3000, 4000]), "origin": undefined,
}, },
{ "type": "time",
config: {}, "values": Array [
state: { 1000,
displayName: 'temperature', 2000,
seriesIndex: 0, 3000,
4000,
],
},
Object {
"config": Object {},
"name": "temperature",
"state": Object {
"displayName": "temperature",
"origin": Object {
"fieldIndex": 1,
"frameIndex": 0,
},
"seriesIndex": 0,
},
"type": "number",
"values": Array [
1,
3,
5,
7,
],
}, },
name: 'temperature', Object {
type: FieldType.number, "config": Object {},
values: new ArrayVector([1, 3, 5, 7]), "name": "temperature B",
}, "state": Object {
{ "displayName": "temperature B",
config: {}, "origin": Object {
state: { "fieldIndex": 1,
displayName: 'temperature B', "frameIndex": 1,
seriesIndex: 1, },
"seriesIndex": 1,
},
"type": "number",
"values": Array [
0,
2,
6,
7,
],
}, },
name: 'temperature B', ]
type: FieldType.number, `);
values: new ArrayVector([0, 2, 6, 7]),
},
]);
}); });
it('should align multiple data frames into one data frame and skip non-numeric fields', () => { it('should align multiple data frames into one data frame and skip non-numeric fields', () => {
...@@ -113,27 +166,45 @@ describe('alignDataFrames', () => { ...@@ -113,27 +166,45 @@ describe('alignDataFrames', () => {
}), }),
]; ];
const aligned = alignDataFrames(data); const aligned = joinDataFrames(data);
expect(aligned?.frame.fields).toEqual([ expect(aligned?.fields).toMatchInlineSnapshot(`
{ Array [
config: {}, Object {
state: {}, "config": Object {},
name: 'time', "name": "time",
type: FieldType.time, "state": Object {
values: new ArrayVector([1000, 2000, 3000, 4000]), "origin": undefined,
}, },
{ "type": "time",
config: {}, "values": Array [
state: { 1000,
displayName: 'temperature', 2000,
seriesIndex: 0, 3000,
4000,
],
},
Object {
"config": Object {},
"name": "temperature",
"state": Object {
"displayName": "temperature",
"origin": Object {
"fieldIndex": 1,
"frameIndex": 0,
},
"seriesIndex": 0,
},
"type": "number",
"values": Array [
1,
3,
5,
7,
],
}, },
name: 'temperature', ]
type: FieldType.number, `);
values: new ArrayVector([1, 3, 5, 7]),
},
]);
}); });
it('should align multiple data frames into one data frame and skip non-numeric fields', () => { it('should align multiple data frames into one data frame and skip non-numeric fields', () => {
...@@ -147,32 +218,50 @@ describe('alignDataFrames', () => { ...@@ -147,32 +218,50 @@ describe('alignDataFrames', () => {
}), }),
]; ];
const aligned = alignDataFrames(data); const aligned = joinDataFrames(data);
expect(aligned?.frame.fields).toEqual([ expect(aligned?.fields).toMatchInlineSnapshot(`
{ Array [
config: {}, Object {
state: {}, "config": Object {},
name: 'time', "name": "time",
type: FieldType.time, "state": Object {
values: new ArrayVector([1000, 2000, 3000, 4000]), "origin": undefined,
}, },
{ "type": "time",
config: {}, "values": Array [
state: { 1000,
displayName: 'temperature', 2000,
seriesIndex: 0, 3000,
4000,
],
},
Object {
"config": Object {},
"name": "temperature",
"state": Object {
"displayName": "temperature",
"origin": Object {
"fieldIndex": 1,
"frameIndex": 0,
},
"seriesIndex": 0,
},
"type": "number",
"values": Array [
1,
3,
5,
7,
],
}, },
name: 'temperature', ]
type: FieldType.number, `);
values: new ArrayVector([1, 3, 5, 7]),
},
]);
}); });
}); });
describe('getDataFrameFieldIndex', () => { describe('getDataFrameFieldIndex', () => {
let aligned: AlignedFrameWithGapTest | null; let aligned: DataFrame | null;
beforeAll(() => { beforeAll(() => {
const data: DataFrame[] = [ const data: DataFrame[] = [
...@@ -197,7 +286,7 @@ describe('alignDataFrames', () => { ...@@ -197,7 +286,7 @@ describe('alignDataFrames', () => {
}), }),
]; ];
aligned = alignDataFrames(data); aligned = joinDataFrames(data);
}); });
it.each` it.each`
...@@ -209,7 +298,7 @@ describe('alignDataFrames', () => { ...@@ -209,7 +298,7 @@ describe('alignDataFrames', () => {
`('should return correct index for yDim', ({ yDim, index }) => { `('should return correct index for yDim', ({ yDim, index }) => {
const [frameIndex, fieldIndex] = index; const [frameIndex, fieldIndex] = index;
expect(aligned?.getDataFrameFieldIndex(yDim)).toEqual({ expect(aligned?.fields[yDim].state?.origin).toEqual({
frameIndex, frameIndex,
fieldIndex, fieldIndex,
}); });
......
...@@ -12,7 +12,6 @@ import { ...@@ -12,7 +12,6 @@ import {
sortDataFrame, sortDataFrame,
Vector, Vector,
} from '@grafana/data'; } from '@grafana/data';
import { AlignedFrameWithGapTest } from '../uPlot/types';
import uPlot, { AlignedData, JoinNullMode } from 'uplot'; import uPlot, { AlignedData, JoinNullMode } from 'uplot';
import { XYFieldMatchers } from './GraphNG'; import { XYFieldMatchers } from './GraphNG';
...@@ -44,7 +43,7 @@ export function mapDimesions(match: XYFieldMatchers, frame: DataFrame, frames?: ...@@ -44,7 +43,7 @@ export function mapDimesions(match: XYFieldMatchers, frame: DataFrame, frames?:
* *
* @alpha * @alpha
*/ */
export function alignDataFrames(frames: DataFrame[], fields?: XYFieldMatchers): AlignedFrameWithGapTest | null { export function joinDataFrames(frames: DataFrame[], fields?: XYFieldMatchers): DataFrame | null {
const valuesFromFrames: AlignedData[] = []; const valuesFromFrames: AlignedData[] = [];
const sourceFields: Field[] = []; const sourceFields: Field[] = [];
const sourceFieldsRefs: Record<number, DataFrameFieldIndex> = {}; const sourceFieldsRefs: Record<number, DataFrameFieldIndex> = {};
...@@ -118,39 +117,34 @@ export function alignDataFrames(frames: DataFrame[], fields?: XYFieldMatchers): ...@@ -118,39 +117,34 @@ export function alignDataFrames(frames: DataFrame[], fields?: XYFieldMatchers):
} }
// do the actual alignment (outerJoin on the first arrays) // do the actual alignment (outerJoin on the first arrays)
let alignedData = uPlot.join(valuesFromFrames, nullModes); let joinedData = uPlot.join(valuesFromFrames, nullModes);
if (alignedData!.length !== sourceFields.length) { if (joinedData!.length !== sourceFields.length) {
throw new Error('outerJoinValues lost a field?'); throw new Error('outerJoinValues lost a field?');
} }
let seriesIdx = 0; let seriesIdx = 0;
// Replace the values from the outer-join field // Replace the values from the outer-join field
return { return {
frame: { ...frames[0],
length: alignedData![0].length, length: joinedData![0].length,
fields: alignedData!.map((vals, idx) => { fields: joinedData!.map((vals, idx) => {
let state: FieldState = { ...sourceFields[idx].state }; let state: FieldState = {
...sourceFields[idx].state,
if (sourceFields[idx].type !== FieldType.time) { origin: sourceFieldsRefs[idx],
state.seriesIndex = seriesIdx; };
seriesIdx++;
} if (sourceFields[idx].type !== FieldType.time) {
state.seriesIndex = seriesIdx;
return { seriesIdx++;
...sourceFields[idx],
state,
values: new ArrayVector(vals),
};
}),
},
getDataFrameFieldIndex: (alignedFieldIndex: number) => {
const index = sourceFieldsRefs[alignedFieldIndex];
if (!index) {
throw new Error(`Could not find index for ${alignedFieldIndex}`);
} }
return index;
}, return {
...sourceFields[idx],
state,
values: new ArrayVector(vals),
};
}),
}; };
} }
......
...@@ -160,10 +160,7 @@ export class Sparkline extends PureComponent<Props, State> { ...@@ -160,10 +160,7 @@ export class Sparkline extends PureComponent<Props, State> {
return ( return (
<UPlotChart <UPlotChart
data={{ data={data}
frame: data,
getDataFrameFieldIndex: () => undefined,
}}
config={configBuilder} config={configBuilder}
width={width} width={width}
height={height} height={height}
......
import React from 'react'; import React from 'react';
import { UPlotChart } from './Plot'; import { UPlotChart } from './Plot';
import { act, render } from '@testing-library/react'; import { act, render } from '@testing-library/react';
import { ArrayVector, DataFrame, dateTime, FieldConfig, FieldType, MutableDataFrame } from '@grafana/data'; import { ArrayVector, dateTime, FieldConfig, FieldType, MutableDataFrame } from '@grafana/data';
import { GraphFieldConfig, DrawStyle } from '../uPlot/config'; import { GraphFieldConfig, DrawStyle } from '../uPlot/config';
import uPlot from 'uplot'; import uPlot from 'uplot';
import createMockRaf from 'mock-raf'; import createMockRaf from 'mock-raf';
import { UPlotConfigBuilder } from './config/UPlotConfigBuilder'; import { UPlotConfigBuilder } from './config/UPlotConfigBuilder';
import { AlignedFrameWithGapTest } from './types';
const mockRaf = createMockRaf(); const mockRaf = createMockRaf();
const setDataMock = jest.fn(); const setDataMock = jest.fn();
...@@ -69,11 +68,10 @@ describe('UPlotChart', () => { ...@@ -69,11 +68,10 @@ describe('UPlotChart', () => {
it('destroys uPlot instance when component unmounts', () => { it('destroys uPlot instance when component unmounts', () => {
const { data, timeRange, config } = mockData(); const { data, timeRange, config } = mockData();
const uPlotData = createPlotData(data);
const { unmount } = render( const { unmount } = render(
<UPlotChart <UPlotChart
data={uPlotData} data={data} // mock
config={config} config={config}
timeRange={timeRange} timeRange={timeRange}
timeZone={'browser'} timeZone={'browser'}
...@@ -95,11 +93,10 @@ describe('UPlotChart', () => { ...@@ -95,11 +93,10 @@ describe('UPlotChart', () => {
describe('data update', () => { describe('data update', () => {
it('skips uPlot reinitialization when there are no field config changes', () => { it('skips uPlot reinitialization when there are no field config changes', () => {
const { data, timeRange, config } = mockData(); const { data, timeRange, config } = mockData();
const uPlotData = createPlotData(data);
const { rerender } = render( const { rerender } = render(
<UPlotChart <UPlotChart
data={uPlotData} data={data} // mock
config={config} config={config}
timeRange={timeRange} timeRange={timeRange}
timeZone={'browser'} timeZone={'browser'}
...@@ -116,11 +113,10 @@ describe('UPlotChart', () => { ...@@ -116,11 +113,10 @@ describe('UPlotChart', () => {
expect(uPlot).toBeCalledTimes(1); expect(uPlot).toBeCalledTimes(1);
data.fields[1].values.set(0, 1); data.fields[1].values.set(0, 1);
uPlotData.frame = data;
rerender( rerender(
<UPlotChart <UPlotChart
data={uPlotData} data={data} // changed
config={config} config={config}
timeRange={timeRange} timeRange={timeRange}
timeZone={'browser'} timeZone={'browser'}
...@@ -136,10 +132,9 @@ describe('UPlotChart', () => { ...@@ -136,10 +132,9 @@ describe('UPlotChart', () => {
describe('config update', () => { describe('config update', () => {
it('skips uPlot intialization for width and height equal 0', async () => { it('skips uPlot intialization for width and height equal 0', async () => {
const { data, timeRange, config } = mockData(); const { data, timeRange, config } = mockData();
const uPlotData = createPlotData(data);
const { queryAllByTestId } = render( const { queryAllByTestId } = render(
<UPlotChart data={uPlotData} config={config} timeRange={timeRange} timeZone={'browser'} width={0} height={0} /> <UPlotChart data={data} config={config} timeRange={timeRange} timeZone={'browser'} width={0} height={0} />
); );
expect(queryAllByTestId('uplot-main-div')).toHaveLength(1); expect(queryAllByTestId('uplot-main-div')).toHaveLength(1);
...@@ -148,11 +143,10 @@ describe('UPlotChart', () => { ...@@ -148,11 +143,10 @@ describe('UPlotChart', () => {
it('reinitializes uPlot when config changes', () => { it('reinitializes uPlot when config changes', () => {
const { data, timeRange, config } = mockData(); const { data, timeRange, config } = mockData();
const uPlotData = createPlotData(data);
const { rerender } = render( const { rerender } = render(
<UPlotChart <UPlotChart
data={uPlotData} data={data} // frame
config={config} config={config}
timeRange={timeRange} timeRange={timeRange}
timeZone={'browser'} timeZone={'browser'}
...@@ -170,7 +164,7 @@ describe('UPlotChart', () => { ...@@ -170,7 +164,7 @@ describe('UPlotChart', () => {
rerender( rerender(
<UPlotChart <UPlotChart
data={uPlotData} data={data}
config={new UPlotConfigBuilder()} config={new UPlotConfigBuilder()}
timeRange={timeRange} timeRange={timeRange}
timeZone={'browser'} timeZone={'browser'}
...@@ -185,11 +179,10 @@ describe('UPlotChart', () => { ...@@ -185,11 +179,10 @@ describe('UPlotChart', () => {
it('skips uPlot reinitialization when only dimensions change', () => { it('skips uPlot reinitialization when only dimensions change', () => {
const { data, timeRange, config } = mockData(); const { data, timeRange, config } = mockData();
const uPlotData = createPlotData(data);
const { rerender } = render( const { rerender } = render(
<UPlotChart <UPlotChart
data={uPlotData} data={data} // frame
config={config} config={config}
timeRange={timeRange} timeRange={timeRange}
timeZone={'browser'} timeZone={'browser'}
...@@ -205,7 +198,7 @@ describe('UPlotChart', () => { ...@@ -205,7 +198,7 @@ describe('UPlotChart', () => {
rerender( rerender(
<UPlotChart <UPlotChart
data={uPlotData} data={data} // frame
config={new UPlotConfigBuilder()} config={new UPlotConfigBuilder()}
timeRange={timeRange} timeRange={timeRange}
timeZone={'browser'} timeZone={'browser'}
...@@ -220,10 +213,3 @@ describe('UPlotChart', () => { ...@@ -220,10 +213,3 @@ describe('UPlotChart', () => {
}); });
}); });
}); });
const createPlotData = (frame: DataFrame): AlignedFrameWithGapTest => {
return {
frame,
getDataFrameFieldIndex: () => undefined,
};
};
...@@ -39,7 +39,7 @@ export const UPlotChart: React.FC<PlotProps> = (props) => { ...@@ -39,7 +39,7 @@ export const UPlotChart: React.FC<PlotProps> = (props) => {
// 1. When config is ready and there is no uPlot instance, create new uPlot and return // 1. When config is ready and there is no uPlot instance, create new uPlot and return
if (isConfigReady && !plotInstance.current) { if (isConfigReady && !plotInstance.current) {
plotInstance.current = initializePlot(prepareData(props.data.frame), currentConfig.current, canvasRef.current); plotInstance.current = initializePlot(prepareData(props.data), currentConfig.current, canvasRef.current);
setIsPlotReady(true); setIsPlotReady(true);
return; return;
} }
...@@ -60,12 +60,12 @@ export const UPlotChart: React.FC<PlotProps> = (props) => { ...@@ -60,12 +60,12 @@ export const UPlotChart: React.FC<PlotProps> = (props) => {
pluginLog('uPlot core', false, 'destroying instance'); pluginLog('uPlot core', false, 'destroying instance');
plotInstance.current.destroy(); plotInstance.current.destroy();
} }
plotInstance.current = initializePlot(prepareData(props.data.frame), currentConfig.current, canvasRef.current); plotInstance.current = initializePlot(prepareData(props.data), currentConfig.current, canvasRef.current);
return; return;
} }
// 4. Otherwise, assume only data has changed and update uPlot data // 4. Otherwise, assume only data has changed and update uPlot data
updateData(props.data.frame, props.config, plotInstance.current, prepareData(props.data.frame)); updateData(props.data, props.config, plotInstance.current, prepareData(props.data));
}, [props, isConfigReady]); }, [props, isConfigReady]);
// When component unmounts, clean the existing uPlot instance // When component unmounts, clean the existing uPlot instance
...@@ -86,7 +86,7 @@ export const UPlotChart: React.FC<PlotProps> = (props) => { ...@@ -86,7 +86,7 @@ export const UPlotChart: React.FC<PlotProps> = (props) => {
); );
}; };
function prepareData(frame: DataFrame) { function prepareData(frame: DataFrame): AlignedData {
return frame.fields.map((f) => f.values.toArray()) as AlignedData; return frame.fields.map((f) => f.values.toArray()) as AlignedData;
} }
......
import React, { useCallback, useContext } from 'react'; import React, { useCallback, useContext } from 'react';
import uPlot, { Series } from 'uplot'; import uPlot, { Series } from 'uplot';
import { PlotPlugin, AlignedFrameWithGapTest } from './types'; import { PlotPlugin } from './types';
import { DataFrame, Field, FieldConfig } from '@grafana/data'; import { DataFrame, Field, FieldConfig } from '@grafana/data';
interface PlotCanvasContextType { interface PlotCanvasContextType {
...@@ -26,7 +26,7 @@ interface PlotContextType extends PlotPluginsContextType { ...@@ -26,7 +26,7 @@ interface PlotContextType extends PlotPluginsContextType {
getSeries: () => Series[]; getSeries: () => Series[];
getCanvas: () => PlotCanvasContextType; getCanvas: () => PlotCanvasContextType;
canvasRef: any; canvasRef: any;
data: AlignedFrameWithGapTest; data: DataFrame;
} }
export const PlotContext = React.createContext<PlotContextType>({} as PlotContextType); export const PlotContext = React.createContext<PlotContextType>({} as PlotContextType);
...@@ -76,7 +76,7 @@ export const usePlotData = (): PlotDataAPI => { ...@@ -76,7 +76,7 @@ export const usePlotData = (): PlotDataAPI => {
if (!ctx) { if (!ctx) {
throwWhenNoContext('usePlotData'); throwWhenNoContext('usePlotData');
} }
return ctx!.data.frame.fields[idx]; return ctx!.data.fields[idx];
}, },
[ctx] [ctx]
); );
...@@ -109,7 +109,7 @@ export const usePlotData = (): PlotDataAPI => { ...@@ -109,7 +109,7 @@ export const usePlotData = (): PlotDataAPI => {
} }
// by uPlot convention x-axis is always first field // by uPlot convention x-axis is always first field
// this may change when we introduce non-time x-axis and multiple x-axes (https://leeoniya.github.io/uPlot/demos/time-periods.html) // this may change when we introduce non-time x-axis and multiple x-axes (https://leeoniya.github.io/uPlot/demos/time-periods.html)
return ctx!.data.frame.fields.slice(1); return ctx!.data.fields.slice(1);
}, [ctx]); }, [ctx]);
if (!ctx) { if (!ctx) {
...@@ -117,7 +117,7 @@ export const usePlotData = (): PlotDataAPI => { ...@@ -117,7 +117,7 @@ export const usePlotData = (): PlotDataAPI => {
} }
return { return {
data: ctx.data.frame, data: ctx.data,
getField, getField,
getFieldValue, getFieldValue,
getFieldConfig, getFieldConfig,
...@@ -129,7 +129,7 @@ export const usePlotData = (): PlotDataAPI => { ...@@ -129,7 +129,7 @@ export const usePlotData = (): PlotDataAPI => {
export const buildPlotContext = ( export const buildPlotContext = (
isPlotReady: boolean, isPlotReady: boolean,
canvasRef: any, canvasRef: any,
data: AlignedFrameWithGapTest, data: DataFrame,
registerPlugin: any, registerPlugin: any,
getPlotInstance: () => uPlot | undefined getPlotInstance: () => uPlot | undefined
): PlotContextType => { ): PlotContextType => {
......
import React from 'react'; import React from 'react';
import uPlot, { Options, Hooks } from 'uplot'; import uPlot, { Options, Hooks } from 'uplot';
import { DataFrame, DataFrameFieldIndex, TimeRange, TimeZone } from '@grafana/data'; import { DataFrame, TimeRange, TimeZone } from '@grafana/data';
import { UPlotConfigBuilder } from './config/UPlotConfigBuilder'; import { UPlotConfigBuilder } from './config/UPlotConfigBuilder';
export type PlotSeriesConfig = Pick<Options, 'series' | 'scales' | 'axes' | 'cursor' | 'bands'>; export type PlotSeriesConfig = Pick<Options, 'series' | 'scales' | 'axes' | 'cursor' | 'bands'>;
...@@ -16,7 +16,7 @@ export interface PlotPluginProps { ...@@ -16,7 +16,7 @@ export interface PlotPluginProps {
} }
export interface PlotProps { export interface PlotProps {
data: AlignedFrameWithGapTest; data: DataFrame;
timeRange: TimeRange; timeRange: TimeRange;
timeZone: TimeZone; timeZone: TimeZone;
width: number; width: number;
...@@ -29,8 +29,3 @@ export abstract class PlotConfigBuilder<P, T> { ...@@ -29,8 +29,3 @@ export abstract class PlotConfigBuilder<P, T> {
constructor(public props: P) {} constructor(public props: P) {}
abstract getConfig(): T; abstract getConfig(): T;
} }
export interface AlignedFrameWithGapTest {
frame: DataFrame;
getDataFrameFieldIndex: (alignedFieldIndex: number) => DataFrameFieldIndex | undefined;
}
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