GraphNG: simplify plotContext (#33347)

This commit is contained in:
Leon Sorokin 2021-04-26 15:36:59 -05:00 committed by GitHub
parent 2abd9bc3b9
commit 69bcaf9253
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 29 additions and 81 deletions

View File

@ -1,6 +1,6 @@
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef } from 'react';
import React, { useEffect, useLayoutEffect, useMemo, useRef } from 'react';
import uPlot, { AlignedData, Options } from 'uplot';
import { buildPlotContext, PlotContext } from './context';
import { PlotContext } from './context';
import { DEFAULT_PLOT_CONFIG, pluginLog } from './utils';
import { PlotProps } from './types';
import usePrevious from 'react-use/lib/usePrevious';
@ -12,7 +12,7 @@ import usePrevious from 'react-use/lib/usePrevious';
* Exposes contexts for plugins registration and uPlot instance access
*/
export const UPlotChart: React.FC<PlotProps> = (props) => {
const canvasRef = useRef<HTMLDivElement>(null);
const plotContainer = useRef<HTMLDivElement>(null);
const plotInstance = useRef<uPlot>();
const prevProps = usePrevious(props);
@ -26,17 +26,13 @@ export const UPlotChart: React.FC<PlotProps> = (props) => {
} as uPlot.Options;
}, [props.config]);
const getPlotInstance = useCallback(() => {
return plotInstance.current;
}, []);
useLayoutEffect(() => {
if (!plotInstance.current || props.width === 0 || props.height === 0) {
return;
}
pluginLog('uPlot core', false, 'updating size');
plotInstance.current!.setSize({
plotInstance.current.setSize({
width: props.width,
height: props.height,
});
@ -45,13 +41,13 @@ export const UPlotChart: React.FC<PlotProps> = (props) => {
// Effect responsible for uPlot updates/initialization logic. It's performed whenever component's props have changed
useLayoutEffect(() => {
// 0. Exit early if the component is not ready to initialize uPlot
if (!canvasRef.current || props.width === 0 || props.height === 0) {
if (!plotContainer.current || props.width === 0 || props.height === 0) {
return;
}
// 1. When config is ready and there is no uPlot instance, create new uPlot and return
if (!plotInstance.current || !prevProps) {
plotInstance.current = initializePlot(props.data, config, canvasRef.current);
plotInstance.current = initializePlot(props.data, config, plotContainer.current);
return;
}
@ -61,7 +57,7 @@ export const UPlotChart: React.FC<PlotProps> = (props) => {
pluginLog('uPlot core', false, 'destroying instance');
plotInstance.current.destroy();
}
plotInstance.current = initializePlot(props.data, config, canvasRef.current);
plotInstance.current = initializePlot(props.data, config, plotContainer.current);
return;
}
@ -77,13 +73,15 @@ export const UPlotChart: React.FC<PlotProps> = (props) => {
// Memoize plot context
const plotCtx = useMemo(() => {
return buildPlotContext(canvasRef, props.data, getPlotInstance);
}, [plotInstance, canvasRef, props.data, getPlotInstance]);
return {
plot: plotInstance.current,
};
}, [plotInstance.current, props.data]);
return (
<PlotContext.Provider value={plotCtx}>
<div style={{ position: 'relative' }}>
<div ref={plotCtx.canvasRef} data-testid="uplot-main-div" />
<div ref={plotContainer} data-testid="uplot-main-div" />
{props.children}
</div>
</PlotContext.Provider>

View File

@ -1,28 +1,8 @@
import React, { useContext } from 'react';
import uPlot, { AlignedData, Series } from 'uplot';
/**
* @alpha
*/
interface PlotCanvasContextType {
// canvas size css pxs
width: number;
height: number;
// plotting area bbox, css pxs
plot: {
width: number;
height: number;
top: number;
left: number;
};
}
import uPlot from 'uplot';
interface PlotContextType {
getPlotInstance: () => uPlot | undefined;
getSeries: () => Series[];
getCanvas: () => PlotCanvasContextType;
canvasRef: any;
data: AlignedData;
plot: uPlot | undefined;
}
/**
@ -34,34 +14,3 @@ export const PlotContext = React.createContext<PlotContextType>({} as PlotContex
export const usePlotContext = (): PlotContextType => {
return useContext<PlotContextType>(PlotContext);
};
/**
* @alpha
*/
export const buildPlotContext = (
canvasRef: any,
data: AlignedData,
getPlotInstance: () => uPlot | undefined
): PlotContextType => {
return {
canvasRef,
data,
getPlotInstance,
getSeries: () => getPlotInstance()!.series,
getCanvas: () => {
const plotInstance = getPlotInstance()!;
const bbox = plotInstance.bbox;
const pxRatio = window.devicePixelRatio;
return {
width: plotInstance.width,
height: plotInstance.height,
plot: {
width: bbox.width / pxRatio,
height: bbox.height / pxRatio,
top: bbox.top / pxRatio,
left: bbox.left / pxRatio,
},
};
},
};
};

View File

@ -27,7 +27,7 @@ export function EventsCanvas({ id, events, renderEventMarker, mapEventToXYCoords
const eventMarkers = useMemo(() => {
const markers: React.ReactNode[] = [];
if (!plotCtx.getPlotInstance() || events.length === 0) {
if (!plotCtx.plot || events.length === 0) {
return markers;
}
@ -49,7 +49,7 @@ export function EventsCanvas({ id, events, renderEventMarker, mapEventToXYCoords
return <>{markers}</>;
}, [events, renderEventMarker, renderToken, plotCtx]);
if (!plotCtx.getPlotInstance()) {
if (!plotCtx.plot) {
return null;
}

View File

@ -9,8 +9,8 @@ interface XYCanvasProps {}
* Useful when you want to render some overlay with canvas-independent elements on top of the plot.
*/
export const XYCanvas: React.FC<XYCanvasProps> = ({ children }) => {
const plotContext = usePlotContext();
const plotInstance = plotContext.getPlotInstance();
const plotCtx = usePlotContext();
const plotInstance = plotCtx.plot;
if (!plotInstance) {
return null;

View File

@ -30,7 +30,7 @@ export const TooltipPlugin: React.FC<TooltipPluginProps> = ({
config,
...otherProps
}) => {
const plotContext = usePlotContext();
const plotCtx = usePlotContext();
const plotCanvas = useRef<HTMLDivElement>();
const plotCanvasBBox = useRef<any>({ left: 0, top: 0, right: 0, bottom: 0, width: 0, height: 0 });
const [focusedSeriesIdx, setFocusedSeriesIdx] = useState<number | null>(null);
@ -72,7 +72,7 @@ export const TooltipPlugin: React.FC<TooltipPluginProps> = ({
});
}, [config]);
if (!plotContext.getPlotInstance() || focusedPointIdx === null) {
if (!plotCtx.plot || focusedPointIdx === null) {
return null;
}
@ -89,10 +89,10 @@ export const TooltipPlugin: React.FC<TooltipPluginProps> = ({
// when interacting with a point in single mode
if (mode === TooltipDisplayMode.Single && focusedSeriesIdx !== null) {
const field = otherProps.data.fields[focusedSeriesIdx];
const plotSeries = plotContext.getSeries();
const plotSeries = plotCtx.plot.series;
const fieldFmt = field.display || getDisplayProcessor({ field, timeZone });
const value = fieldFmt(plotContext.data[focusedSeriesIdx!][focusedPointIdx]);
const value = fieldFmt(plotCtx.plot.data[focusedSeriesIdx!][focusedPointIdx]);
tooltip = (
<SeriesTable
@ -111,7 +111,7 @@ export const TooltipPlugin: React.FC<TooltipPluginProps> = ({
if (mode === TooltipDisplayMode.Multi) {
let series: SeriesTableRowProps[] = [];
const plotSeries = plotContext.getSeries();
const plotSeries = plotCtx.plot.series;
for (let i = 0; i < plotSeries.length; i++) {
const frame = otherProps.data;
@ -125,7 +125,7 @@ export const TooltipPlugin: React.FC<TooltipPluginProps> = ({
continue;
}
const value = field.display!(plotContext.data[i][focusedPointIdx]);
const value = field.display!(plotCtx.plot.data[i][focusedPointIdx]);
series.push({
// TODO: align with uPlot typings

View File

@ -17,7 +17,7 @@ interface AnnotationsDataFrameViewDTO {
export const AnnotationsPlugin: React.FC<AnnotationsPluginProps> = ({ annotations, timeZone, config }) => {
const theme = useTheme();
const { getPlotInstance } = usePlotContext();
const plotCtx = usePlotContext();
const annotationsRef = useRef<Array<DataFrameView<AnnotationsDataFrameViewDTO>>>();
@ -84,7 +84,7 @@ export const AnnotationsPlugin: React.FC<AnnotationsPluginProps> = ({ annotation
(frame: DataFrame, index: number) => {
const view = new DataFrameView<AnnotationsDataFrameViewDTO>(frame);
const annotation = view.get(index);
const plotInstance = getPlotInstance();
const plotInstance = plotCtx.plot;
if (!annotation.time || !plotInstance) {
return undefined;
}
@ -94,7 +94,7 @@ export const AnnotationsPlugin: React.FC<AnnotationsPluginProps> = ({ annotation
y: plotInstance.bbox.height / window.devicePixelRatio + 4,
};
},
[getPlotInstance]
[plotCtx.plot]
);
const renderMarker = useCallback(

View File

@ -19,9 +19,10 @@ interface ExemplarsPluginProps {
export const ExemplarsPlugin: React.FC<ExemplarsPluginProps> = ({ exemplars, timeZone, getFieldLinks, config }) => {
const plotCtx = usePlotContext();
const mapExemplarToXYCoords = useCallback(
(dataFrame: DataFrame, index: number) => {
const plotInstance = plotCtx.getPlotInstance();
const plotInstance = plotCtx.plot;
const time = dataFrame.fields.find((f) => f.name === TIME_SERIES_TIME_FIELD_NAME);
const value = dataFrame.fields.find((f) => f.name === TIME_SERIES_VALUE_FIELD_NAME);