From 94d3c6adf91ea97742359c7192be1547062e08ba Mon Sep 17 00:00:00 2001 From: Stephanie Roy Date: Tue, 21 Feb 2023 11:40:46 -0500 Subject: [PATCH 1/7] Optimize rendering time of plots by making sure snap points do not trigger re-rendering --- webview/src/plots/components/ZoomablePlot.tsx | 34 +++++++++--- .../checkpointPlots/CheckpointPlot.tsx | 14 +++-- .../templatePlots/TemplatePlotsGrid.tsx | 14 +++-- webview/src/plots/hooks/useResize.ts | 52 ------------------- webview/src/plots/hooks/useSnapPoints.ts | 17 ++++++ 5 files changed, 55 insertions(+), 76 deletions(-) delete mode 100644 webview/src/plots/hooks/useResize.ts create mode 100644 webview/src/plots/hooks/useSnapPoints.ts diff --git a/webview/src/plots/components/ZoomablePlot.tsx b/webview/src/plots/components/ZoomablePlot.tsx index 0ce5636d12..22c4c0ede6 100644 --- a/webview/src/plots/components/ZoomablePlot.tsx +++ b/webview/src/plots/components/ZoomablePlot.tsx @@ -1,5 +1,8 @@ +import { AnyAction } from '@reduxjs/toolkit' import cx from 'classnames' -import React, { useEffect, useRef, useState } from 'react' +import { MessageFromWebviewType } from 'dvc/src/webview/contract' +import { Section } from 'dvc/src/plots/webview/contract' +import React, { useCallback, useEffect, useRef, useState } from 'react' import { useDispatch } from 'react-redux' import { PlainObject, VisualizationSpec } from 'react-vega' import { Renderers } from 'vega' @@ -9,6 +12,7 @@ import styles from './styles.module.scss' import { Resizer } from './Resizer' import { config } from './constants' import { GripIcon } from '../../shared/components/dragDrop/GripIcon' +import { sendMessage } from '../../shared/vscode' interface ZoomablePlotProps { spec: VisualizationSpec @@ -16,10 +20,11 @@ interface ZoomablePlotProps { id: string onViewReady?: () => void toggleDrag: (enabled: boolean) => void - onResize: (diff: number) => void - snapPoints: number[] + changeSize: (size: number) => AnyAction currentSnapPoint: number - size: number + section: Section + snapPoints: [number, number, number, number] + shouldNotResize?: boolean } export const ZoomablePlot: React.FC = ({ @@ -28,10 +33,11 @@ export const ZoomablePlot: React.FC = ({ id, onViewReady, toggleDrag, - onResize, - snapPoints, + changeSize, currentSnapPoint, - size + section, + snapPoints, + shouldNotResize }) => { const dispatch = useDispatch() const previousSpecsAndData = useRef(JSON.stringify({ data, spec })) @@ -40,6 +46,7 @@ export const ZoomablePlot: React.FC = ({ const enableClickTimeout = useRef(0) const [isExpanding, setIsExpanding] = useState(false) const newSpecsAndData = JSON.stringify({ data, spec }) + const size = snapPoints[currentSnapPoint - 1] const plotProps: VegaLiteProps = { actions: false, @@ -69,6 +76,17 @@ export const ZoomablePlot: React.FC = ({ const handleOnClick = () => !clickDisabled.current && dispatch(setZoomedInPlot({ id, plot: plotProps })) + const onResize = useCallback( + (newSnapPoint: number) => { + dispatch(changeSize(newSnapPoint)) + sendMessage({ + payload: { section, size: newSnapPoint }, + type: MessageFromWebviewType.RESIZE_PLOTS + }) + }, + [dispatch, changeSize, section] + ) + const commonResizerProps = { onGrab: () => { clickDisabled.current = true @@ -94,7 +112,7 @@ export const ZoomablePlot: React.FC = ({ {currentPlotProps.current && ( )} - {snapPoints.length > 0 && ( + {!shouldNotResize && ( = ({ ) const [plot, setPlot] = useState(plotDataStore.checkpoint[id]) const currentSize = useSelector((state: PlotsState) => state.checkpoint.size) - const { onResize: handleResize, snapPoints } = useResize( - Section.CHECKPOINT_PLOTS, - changeSize - ) + const snapPoints = useSnapPoints() + const spec = useMemo(() => { const title = plot?.title if (!title) { @@ -60,10 +58,10 @@ export const CheckpointPlot: React.FC = ({ data={{ values }} id={key} toggleDrag={toggleDrag} - onResize={handleResize} - snapPoints={snapPoints} + changeSize={changeSize} currentSnapPoint={currentSize} - size={snapPoints[currentSize - 1]} + section={Section.CHECKPOINT_PLOTS} + snapPoints={snapPoints} /> ) diff --git a/webview/src/plots/components/templatePlots/TemplatePlotsGrid.tsx b/webview/src/plots/components/templatePlots/TemplatePlotsGrid.tsx index 6a962f083e..e8b6a1c263 100644 --- a/webview/src/plots/components/templatePlots/TemplatePlotsGrid.tsx +++ b/webview/src/plots/components/templatePlots/TemplatePlotsGrid.tsx @@ -16,7 +16,7 @@ import { DropTarget } from '../DropTarget' import styles from '../styles.module.scss' import { ZoomablePlot } from '../ZoomablePlot' import { PlotsState } from '../../store' -import { useResize } from '../../hooks/useResize' +import { useSnapPoints } from '../../hooks/useSnapPoints' interface TemplatePlotsGridProps { entries: TemplatePlotEntry[] @@ -48,15 +48,12 @@ export const TemplatePlotsGrid: React.FC = ({ }) => { const dispatch = useDispatch() const [order, setOrder] = useState([]) + const snapPoints = useSnapPoints() const disabledDragPlotIds = useSelector( (state: PlotsState) => state.template.disabledDragPlotIds ) const currentSize = useSelector((state: PlotsState) => state.template.size) - const { onResize: handleResize, snapPoints } = useResize( - Section.TEMPLATE_PLOTS, - changeSize - ) const addDisabled = useCallback( (e: Event) => { @@ -139,10 +136,11 @@ export const TemplatePlotsGrid: React.FC = ({ spec={{ ...content, ...autoSize } as VisualizationSpec} onViewReady={addEventsOnViewReady} toggleDrag={toggleDrag} - onResize={handleResize} - snapPoints={multiView ? [] : snapPoints} + changeSize={changeSize} currentSnapPoint={currentSize} - size={snapPoints[currentSize - 1]} + shouldNotResize={multiView} + section={Section.TEMPLATE_PLOTS} + snapPoints={snapPoints} /> ) diff --git a/webview/src/plots/hooks/useResize.ts b/webview/src/plots/hooks/useResize.ts deleted file mode 100644 index 4e54326479..0000000000 --- a/webview/src/plots/hooks/useResize.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { AnyAction } from '@reduxjs/toolkit' -import { Section } from 'dvc/src/plots/webview/contract' -import { MessageFromWebviewType } from 'dvc/src/webview/contract' -import { useCallback, useEffect, useState } from 'react' -import { useDispatch, useSelector } from 'react-redux' -import { sendMessage } from '../../shared/vscode' -import { PlotsState } from '../store' - -interface SnapPoints { - 1: number - 2: number - 3: number - 4: number -} - -const initialSnapPoints = { - 1: 0, - 2: 0, - 3: 0, - 4: 0 -} - -export const useResize = ( - section: Section, - changeSize: (size: number) => AnyAction -) => { - const dispatch = useDispatch() - const maxSize = useSelector((state: PlotsState) => state.webview.maxPlotSize) - const [snapPoints, setSnapPoints] = useState(initialSnapPoints) - - useEffect(() => { - setSnapPoints({ - 1: maxSize, - 2: maxSize / 2, - 3: maxSize / 3, - 4: maxSize / 4 - }) - }, [maxSize]) - - const onResize = useCallback( - (newSnapPoint: number) => { - dispatch(changeSize(newSnapPoint)) - sendMessage({ - payload: { section, size: newSnapPoint }, - type: MessageFromWebviewType.RESIZE_PLOTS - }) - }, - [dispatch, changeSize, section] - ) - - return { onResize, snapPoints: Object.values(snapPoints) } -} diff --git a/webview/src/plots/hooks/useSnapPoints.ts b/webview/src/plots/hooks/useSnapPoints.ts new file mode 100644 index 0000000000..055cb51a57 --- /dev/null +++ b/webview/src/plots/hooks/useSnapPoints.ts @@ -0,0 +1,17 @@ +import { useEffect, useState } from 'react' +import { useSelector } from 'react-redux' +import { PlotsState } from '../store' + +const initialSnapPoints: [number, number, number, number] = [0, 0, 0, 0] + +export const useSnapPoints = () => { + const maxSize = useSelector((state: PlotsState) => state.webview.maxPlotSize) + const [snapPoints, setSnapPoints] = + useState<[number, number, number, number]>(initialSnapPoints) + + useEffect(() => { + setSnapPoints([maxSize, maxSize / 2, maxSize / 3, maxSize / 4]) + }, [maxSize]) + + return snapPoints +} From 449241e0429cf88fe24311bafe23023d0c2d314e Mon Sep 17 00:00:00 2001 From: Stephanie Roy Date: Tue, 21 Feb 2023 13:34:41 -0500 Subject: [PATCH 2/7] Use a type for snappoints --- webview/src/plots/components/ZoomablePlot.tsx | 3 ++- webview/src/plots/hooks/useSnapPoints.ts | 5 ++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/webview/src/plots/components/ZoomablePlot.tsx b/webview/src/plots/components/ZoomablePlot.tsx index 22c4c0ede6..fef065baea 100644 --- a/webview/src/plots/components/ZoomablePlot.tsx +++ b/webview/src/plots/components/ZoomablePlot.tsx @@ -13,6 +13,7 @@ import { Resizer } from './Resizer' import { config } from './constants' import { GripIcon } from '../../shared/components/dragDrop/GripIcon' import { sendMessage } from '../../shared/vscode' +import { SnapPoints } from '../hooks/useSnapPoints' interface ZoomablePlotProps { spec: VisualizationSpec @@ -23,7 +24,7 @@ interface ZoomablePlotProps { changeSize: (size: number) => AnyAction currentSnapPoint: number section: Section - snapPoints: [number, number, number, number] + snapPoints: SnapPoints shouldNotResize?: boolean } diff --git a/webview/src/plots/hooks/useSnapPoints.ts b/webview/src/plots/hooks/useSnapPoints.ts index 055cb51a57..53ebaf3633 100644 --- a/webview/src/plots/hooks/useSnapPoints.ts +++ b/webview/src/plots/hooks/useSnapPoints.ts @@ -2,12 +2,11 @@ import { useEffect, useState } from 'react' import { useSelector } from 'react-redux' import { PlotsState } from '../store' -const initialSnapPoints: [number, number, number, number] = [0, 0, 0, 0] +export type SnapPoints = [number, number, number, number] export const useSnapPoints = () => { const maxSize = useSelector((state: PlotsState) => state.webview.maxPlotSize) - const [snapPoints, setSnapPoints] = - useState<[number, number, number, number]>(initialSnapPoints) + const [snapPoints, setSnapPoints] = useState([0, 0, 0, 0]) useEffect(() => { setSnapPoints([maxSize, maxSize / 2, maxSize / 3, maxSize / 4]) From 6c65589a3d87bfaf6d4318944f6eccaf4f23ae08 Mon Sep 17 00:00:00 2001 From: Stephanie Roy Date: Tue, 21 Feb 2023 14:29:34 -0500 Subject: [PATCH 3/7] Optimize rendering of plots --- .../templatePlots/TemplatePlotsGrid.tsx | 38 ++++---------- .../templatePlots/TemplatePlotsGridItem.tsx | 50 +++++++++++++++++++ webview/src/util/wdyr.ts | 2 +- 3 files changed, 62 insertions(+), 28 deletions(-) create mode 100644 webview/src/plots/components/templatePlots/TemplatePlotsGridItem.tsx diff --git a/webview/src/plots/components/templatePlots/TemplatePlotsGrid.tsx b/webview/src/plots/components/templatePlots/TemplatePlotsGrid.tsx index e8b6a1c263..ea1e1aec0a 100644 --- a/webview/src/plots/components/templatePlots/TemplatePlotsGrid.tsx +++ b/webview/src/plots/components/templatePlots/TemplatePlotsGrid.tsx @@ -1,22 +1,21 @@ import cx from 'classnames' -import { Section, TemplatePlotEntry } from 'dvc/src/plots/webview/contract' +import { TemplatePlotEntry } from 'dvc/src/plots/webview/contract' import { reorderObjectList } from 'dvc/src/util/array' import React, { useEffect, useState, useCallback } from 'react' import { useDispatch, useSelector } from 'react-redux' -import { VisualizationSpec } from 'react-vega' import { changeDisabledDragIds, changeSize } from './templatePlotsSlice' +import { TemplatePlotsGridItem } from './TemplatePlotsGridItem' import { VirtualizedGrid } from '../../../shared/components/virtualizedGrid/VirtualizedGrid' import { DragDropContainer, OnDrop, WrapperProps } from '../../../shared/components/dragDrop/DragDropContainer' -import { withScale } from '../../../util/styles' import { DropTarget } from '../DropTarget' import styles from '../styles.module.scss' -import { ZoomablePlot } from '../ZoomablePlot' import { PlotsState } from '../../store' import { useSnapPoints } from '../../hooks/useSnapPoints' +import { withScale } from '../../../util/styles' interface TemplatePlotsGridProps { entries: TemplatePlotEntry[] @@ -30,11 +29,6 @@ interface TemplatePlotsGridProps { parentDraggedOver?: boolean } -const autoSize = { - height: 'container', - width: 'container' -} - export const TemplatePlotsGrid: React.FC = ({ entries, groupId, @@ -53,7 +47,6 @@ export const TemplatePlotsGrid: React.FC = ({ const disabledDragPlotIds = useSelector( (state: PlotsState) => state.template.disabledDragPlotIds ) - const currentSize = useSelector((state: PlotsState) => state.template.size) const addDisabled = useCallback( (e: Event) => { @@ -87,14 +80,14 @@ export const TemplatePlotsGrid: React.FC = ({ } }, [addDisabled, removeDisabled, disableClick]) - const addEventsOnViewReady = () => { + const addEventsOnViewReady = useCallback(() => { const panels = document.querySelectorAll('.vega-bindings') for (const panel of Object.values(panels)) { panel.addEventListener('mouseenter', addDisabled) panel.addEventListener('mouseleave', removeDisabled) panel.addEventListener('click', disableClick) } - } + }, [addDisabled, removeDisabled, disableClick]) const setEntriesOrder = (order: string[]) => { setOrder(order) @@ -116,31 +109,22 @@ export const TemplatePlotsGrid: React.FC = ({ }) const items = reorderedItems.map((plot: TemplatePlotEntry) => { - const { id, content, multiView, revisions } = plot + const { id, revisions } = plot const nbRevisions = (multiView && revisions?.length) || 1 - const toggleDrag = (enabled: boolean) => { - dispatch(changeDisabledDragIds(enabled ? [] : [id])) - } - return (
-
) diff --git a/webview/src/plots/components/templatePlots/TemplatePlotsGridItem.tsx b/webview/src/plots/components/templatePlots/TemplatePlotsGridItem.tsx new file mode 100644 index 0000000000..2a6b7bbbd4 --- /dev/null +++ b/webview/src/plots/components/templatePlots/TemplatePlotsGridItem.tsx @@ -0,0 +1,50 @@ +import { AnyAction } from '@reduxjs/toolkit' +import { Section, TemplatePlotEntry } from 'dvc/src/plots/webview/contract' +import React, { memo, useMemo } from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { VisualizationSpec } from 'react-vega' +import { changeDisabledDragIds } from './templatePlotsSlice' +import { ZoomablePlot } from '../ZoomablePlot' +import { SnapPoints } from '../../hooks/useSnapPoints' +import { PlotsState } from '../../store' + +interface TemplatePlotsGridItemProps { + plot: TemplatePlotEntry + addEventsOnViewReady: () => void + snapPoints: SnapPoints + changeSize: (size: number) => AnyAction +} + +const TemplatePlotsGridItemComponent: React.FC = ({ + plot, + addEventsOnViewReady, + snapPoints, + changeSize +}) => { + const dispatch = useDispatch() + const currentSize = useSelector((state: PlotsState) => state.template.size) + const { id, content, multiView } = plot + const toggleDrag = (enabled: boolean) => { + dispatch(changeDisabledDragIds(enabled ? [] : [id])) + } + const spec = useMemo( + () => ({ ...content, height: 'container', width: 'container' }), + [content] + ) as VisualizationSpec + + return ( + + ) +} + +export const TemplatePlotsGridItem = memo(TemplatePlotsGridItemComponent) diff --git a/webview/src/util/wdyr.ts b/webview/src/util/wdyr.ts index 801a885d4c..d65f830622 100644 --- a/webview/src/util/wdyr.ts +++ b/webview/src/util/wdyr.ts @@ -5,6 +5,6 @@ import React from 'react' if (process.env.NODE_ENV === 'development') { const whyDidYouRender = require('@welldone-software/why-did-you-render') whyDidYouRender(React, { - trackAllPureComponents: true + trackAllPureComponents: false }) } From fa821a22f92bb4f7c6a8513371c943ae561e774e Mon Sep 17 00:00:00 2001 From: Stephanie Roy Date: Wed, 22 Feb 2023 11:25:43 -0500 Subject: [PATCH 4/7] Do not drill down vega plot content --- webview/src/plots/components/App.test.tsx | 19 +-- webview/src/plots/components/Plots.tsx | 4 +- webview/src/plots/components/ZoomablePlot.tsx | 29 +++-- .../checkpointPlots/CheckpointPlot.tsx | 10 +- .../checkpointPlots/checkpointPlotsSlice.ts | 9 +- webview/src/plots/components/plotDataStore.ts | 22 ++-- .../components/templatePlots/AddedSection.tsx | 6 +- .../templatePlots/TemplatePlots.tsx | 120 +++++++++--------- .../templatePlots/TemplatePlotsGrid.tsx | 95 ++++++++------ .../templatePlots/TemplatePlotsGridItem.tsx | 50 -------- .../templatePlots/templatePlotsSlice.ts | 24 +++- .../plots/components/templatePlots/util.ts | 28 ++-- webview/src/plots/components/webviewSlice.ts | 19 ++- webview/src/plots/hooks/useGetPlot.ts | 51 ++++++++ webview/src/plots/hooks/useSnapPoints.ts | 16 --- 15 files changed, 256 insertions(+), 246 deletions(-) delete mode 100644 webview/src/plots/components/templatePlots/TemplatePlotsGridItem.tsx create mode 100644 webview/src/plots/hooks/useGetPlot.ts delete mode 100644 webview/src/plots/hooks/useSnapPoints.ts diff --git a/webview/src/plots/components/App.test.tsx b/webview/src/plots/components/App.test.tsx index 68ac919ed4..d0d562c877 100644 --- a/webview/src/plots/components/App.test.tsx +++ b/webview/src/plots/components/App.test.tsx @@ -27,8 +27,7 @@ import { Revision, Section, TemplatePlotGroup, - TemplatePlotsData, - TemplatePlotSection + TemplatePlotsData } from 'dvc/src/plots/webview/contract' import { MessageFromWebviewType, @@ -41,8 +40,12 @@ import { VisualizationSpec } from 'react-vega' import { App } from './App' import { NewSectionBlock } from './templatePlots/TemplatePlots' import { SectionDescription } from './PlotsContainer' -import { CheckpointPlotsById, plotDataStore } from './plotDataStore' -import { setMaxPlotSize } from './webviewSlice' +import { + CheckpointPlotsById, + plotDataStore, + TemplatePlotsById +} from './plotDataStore' +import { setSnapPoints } from './webviewSlice' import { plotsReducers, plotsStore } from '../store' import { vsCodeApi } from '../../shared/api' import { @@ -126,7 +129,7 @@ describe('App', () => { const setWrapperSize = (store: typeof plotsStore) => act(() => { - store.dispatch(setMaxPlotSize(1000)) + store.dispatch(setSnapPoints(1000)) }) const templatePlot = templatePlotsFixture.plots[0].entries[0] @@ -201,8 +204,8 @@ describe('App', () => { jest .spyOn(HTMLElement.prototype, 'clientHeight', 'get') .mockImplementation(() => heightToSuppressVegaError) - plotDataStore.checkpoint = {} as CheckpointPlotsById - plotDataStore.template = [] as TemplatePlotSection[] + plotDataStore[Section.CHECKPOINT_PLOTS] = {} as CheckpointPlotsById + plotDataStore[Section.TEMPLATE_PLOTS] = {} as TemplatePlotsById }) afterEach(() => { @@ -1427,7 +1430,7 @@ describe('App', () => { const resizeScreen = (width: number, store: typeof plotsStore) => { act(() => { - store.dispatch(setMaxPlotSize(width)) + store.dispatch(setSnapPoints(width)) }) act(() => { global.innerWidth = width diff --git a/webview/src/plots/components/Plots.tsx b/webview/src/plots/components/Plots.tsx index 1d5bc127af..8f990648df 100644 --- a/webview/src/plots/components/Plots.tsx +++ b/webview/src/plots/components/Plots.tsx @@ -6,7 +6,7 @@ import { CheckpointPlotsWrapper } from './checkpointPlots/CheckpointPlotsWrapper import { TemplatePlotsWrapper } from './templatePlots/TemplatePlotsWrapper' import { ComparisonTableWrapper } from './comparisonTable/ComparisonTableWrapper' import { Ribbon } from './ribbon/Ribbon' -import { setMaxPlotSize, setZoomedInPlot } from './webviewSlice' +import { setSnapPoints, setZoomedInPlot } from './webviewSlice' import { EmptyState } from '../../shared/components/emptyState/EmptyState' import { Modal } from '../../shared/components/modal/Modal' import { WebviewWrapper } from '../../shared/components/webviewWrapper/WebviewWrapper' @@ -38,7 +38,7 @@ const PlotsContent = () => { const onResize = () => { wrapperRef.current && dispatch( - setMaxPlotSize(wrapperRef.current.getBoundingClientRect().width - 100) + setSnapPoints(wrapperRef.current.getBoundingClientRect().width - 100) ) } window.addEventListener('resize', onResize) diff --git a/webview/src/plots/components/ZoomablePlot.tsx b/webview/src/plots/components/ZoomablePlot.tsx index fef065baea..ef90539cb1 100644 --- a/webview/src/plots/components/ZoomablePlot.tsx +++ b/webview/src/plots/components/ZoomablePlot.tsx @@ -3,43 +3,44 @@ import cx from 'classnames' import { MessageFromWebviewType } from 'dvc/src/webview/contract' import { Section } from 'dvc/src/plots/webview/contract' import React, { useCallback, useEffect, useRef, useState } from 'react' -import { useDispatch } from 'react-redux' -import { PlainObject, VisualizationSpec } from 'react-vega' +import { useDispatch, useSelector } from 'react-redux' +import { VisualizationSpec } from 'react-vega' import { Renderers } from 'vega' import VegaLite, { VegaLiteProps } from 'react-vega/lib/VegaLite' import { setZoomedInPlot } from './webviewSlice' import styles from './styles.module.scss' import { Resizer } from './Resizer' import { config } from './constants' +import { PlotsState } from '../store' +import { useGetPlot } from '../hooks/useGetPlot' import { GripIcon } from '../../shared/components/dragDrop/GripIcon' import { sendMessage } from '../../shared/vscode' -import { SnapPoints } from '../hooks/useSnapPoints' interface ZoomablePlotProps { - spec: VisualizationSpec - data?: PlainObject + spec?: VisualizationSpec id: string onViewReady?: () => void - toggleDrag: (enabled: boolean) => void + toggleDrag: (enabled: boolean, id: string) => void changeSize: (size: number) => AnyAction currentSnapPoint: number section: Section - snapPoints: SnapPoints shouldNotResize?: boolean } export const ZoomablePlot: React.FC = ({ - spec, - data, + spec: createdSpec, id, onViewReady, toggleDrag, changeSize, currentSnapPoint, section, - snapPoints, shouldNotResize }) => { + const snapPoints = useSelector( + (state: PlotsState) => state.webview.snapPoints + ) + const { data, content: spec } = useGetPlot(section, id, createdSpec) const dispatch = useDispatch() const previousSpecsAndData = useRef(JSON.stringify({ data, spec })) const currentPlotProps = useRef() @@ -60,6 +61,7 @@ export const ZoomablePlot: React.FC = ({ currentPlotProps.current = plotProps useEffect(() => { + // TODO Review this as this should be handled by the useGetPlot if (previousSpecsAndData.current !== newSpecsAndData) { dispatch( setZoomedInPlot({ id, plot: currentPlotProps.current, refresh: true }) @@ -91,10 +93,10 @@ export const ZoomablePlot: React.FC = ({ const commonResizerProps = { onGrab: () => { clickDisabled.current = true - toggleDrag(false) + toggleDrag(false, id) }, onRelease: () => { - toggleDrag(true) + toggleDrag(true, id) enableClickTimeout.current = window.setTimeout( () => (clickDisabled.current = false), 0 @@ -102,6 +104,9 @@ export const ZoomablePlot: React.FC = ({ }, onResize } + if (!data && !spec) { + return null + } return (