mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
GraphNG: simplify plotContext (#33347)
This commit is contained in:
parent
2abd9bc3b9
commit
69bcaf9253
@ -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>
|
||||
|
@ -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,
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
|
@ -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);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user