diff --git a/packages/grafana-ui/src/components/uPlot/Plot.tsx b/packages/grafana-ui/src/components/uPlot/Plot.tsx index e030ef4bc0b..f461d8ffff9 100755 --- a/packages/grafana-ui/src/components/uPlot/Plot.tsx +++ b/packages/grafana-ui/src/components/uPlot/Plot.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef } from 'react'; +import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; import uPlot, { AlignedData, AlignedDataWithGapTest, Options } from 'uplot'; import { buildPlotContext, PlotContext } from './context'; import { pluginLog } from './utils'; @@ -15,6 +15,7 @@ import usePrevious from 'react-use/lib/usePrevious'; export const UPlotChart: React.FC = props => { const canvasRef = useRef(null); const plotInstance = useRef(); + const [isPlotReady, setIsPlotReady] = useState(false); const prevProps = usePrevious(props); const { isConfigReady, currentConfig, registerPlugin } = usePlotConfig( props.width, @@ -36,6 +37,7 @@ export const UPlotChart: React.FC = props => { // 1. When config is ready and there is no uPlot instance, create new uPlot and return if (isConfigReady && !plotInstance.current) { plotInstance.current = initializePlot(prepareData(props.data), currentConfig.current, canvasRef.current); + setIsPlotReady(true); return; } @@ -68,8 +70,8 @@ export const UPlotChart: React.FC = props => { // Memoize plot context const plotCtx = useMemo(() => { - return buildPlotContext(canvasRef, props.data, registerPlugin, getPlotInstance); - }, [plotInstance, canvasRef, props.data, registerPlugin, getPlotInstance]); + return buildPlotContext(isPlotReady, canvasRef, props.data, registerPlugin, getPlotInstance); + }, [plotInstance, canvasRef, props.data, registerPlugin, getPlotInstance, isPlotReady]); return ( diff --git a/packages/grafana-ui/src/components/uPlot/context.ts b/packages/grafana-ui/src/components/uPlot/context.ts index 558e88c2104..d36b08825de 100644 --- a/packages/grafana-ui/src/components/uPlot/context.ts +++ b/packages/grafana-ui/src/components/uPlot/context.ts @@ -21,6 +21,7 @@ interface PlotPluginsContextType { } interface PlotContextType extends PlotPluginsContextType { + isPlotReady: boolean; getPlotInstance: () => uPlot | undefined; getSeries: () => Series[]; getCanvas: () => PlotCanvasContextType; @@ -126,12 +127,14 @@ export const usePlotData = (): PlotDataAPI => { }; export const buildPlotContext = ( + isPlotReady: boolean, canvasRef: any, data: AlignedFrameWithGapTest, registerPlugin: any, getPlotInstance: () => uPlot | undefined ): PlotContextType => { return { + isPlotReady, canvasRef, data, registerPlugin, diff --git a/public/app/plugins/panel/graph3/plugins/AnnotationsPlugin.tsx b/public/app/plugins/panel/graph3/plugins/AnnotationsPlugin.tsx index 3175239b16b..813f5ed67cd 100644 --- a/public/app/plugins/panel/graph3/plugins/AnnotationsPlugin.tsx +++ b/public/app/plugins/panel/graph3/plugins/AnnotationsPlugin.tsx @@ -32,7 +32,7 @@ export const AnnotationsPlugin: React.FC = ({ annotation ); useEffect(() => { - if (plotCtx.getPlotInstance()) { + if (plotCtx.isPlotReady) { const views: Array> = []; for (const frame of annotations) { @@ -41,7 +41,7 @@ export const AnnotationsPlugin: React.FC = ({ annotation annotationsRef.current = views; } - }, [plotCtx, annotations]); + }, [plotCtx.isPlotReady, annotations]); useEffect(() => { const unregister = plotCtx.registerPlugin({ @@ -70,7 +70,7 @@ export const AnnotationsPlugin: React.FC = ({ annotation continue; } - const xpos = u.valToPos(annotation.time / 1000, 'x', true); + const xpos = u.valToPos(annotation.time, 'x', true); ctx.beginPath(); ctx.lineWidth = 2; ctx.strokeStyle = theme.palette.red; diff --git a/public/app/plugins/panel/graph3/plugins/ExemplarsPlugin.tsx b/public/app/plugins/panel/graph3/plugins/ExemplarsPlugin.tsx index 14b0a5cc8aa..54b74e2c03d 100644 --- a/public/app/plugins/panel/graph3/plugins/ExemplarsPlugin.tsx +++ b/public/app/plugins/panel/graph3/plugins/ExemplarsPlugin.tsx @@ -42,7 +42,7 @@ export const ExemplarsPlugin: React.FC = ({ exemplars, tim // THIS EVENT ONLY MOCKS EXEMPLAR Y VALUE!!!! TO BE REMOVED WHEN WE GET CORRECT EXEMPLARS SHAPE VIA PROPS useEffect(() => { - if (plotCtx.getPlotInstance()) { + if (plotCtx.isPlotReady) { const mocks: DataFrame[] = []; for (const frame of exemplars) { @@ -61,22 +61,23 @@ export const ExemplarsPlugin: React.FC = ({ exemplars, tim setExemplarsMock(mocks); } - }, [plotCtx, exemplars]); + }, [plotCtx.isPlotReady, exemplars]); const mapExemplarToXYCoords = useCallback( (exemplar: ExemplarsDataFrameViewDTO) => { const plotInstance = plotCtx.getPlotInstance(); - if (!exemplar.time || !plotInstance) { + + if (!exemplar.time || !plotCtx.isPlotReady || !plotInstance) { return undefined; } return { - x: plotInstance.valToPos(exemplar.time / 1000, 'x'), + x: plotInstance.valToPos(exemplar.time, 'x'), // exemplar.y is a temporary mock for an examplar. This Needs to be calculated according to examplar scale! y: Math.floor((exemplar.y * plotInstance.bbox.height) / window.devicePixelRatio), }; }, - [plotCtx.getPlotInstance] + [plotCtx.isPlotReady, plotCtx.getPlotInstance] ); const renderMarker = useCallback(