From e7835fc640e3ce0d50a98a55392a21ef8658af4f Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Mon, 4 Sep 2023 22:03:40 +0200 Subject: refactor: disable all animations for large graphs --- .../frontend/src/graph/DotGraphVisualizer.tsx | 53 ++++++++++++++-------- subprojects/frontend/src/graph/ZoomCanvas.tsx | 14 ++++-- 2 files changed, 43 insertions(+), 24 deletions(-) (limited to 'subprojects') diff --git a/subprojects/frontend/src/graph/DotGraphVisualizer.tsx b/subprojects/frontend/src/graph/DotGraphVisualizer.tsx index 02f31085..eec72a7d 100644 --- a/subprojects/frontend/src/graph/DotGraphVisualizer.tsx +++ b/subprojects/frontend/src/graph/DotGraphVisualizer.tsx @@ -9,7 +9,7 @@ import { type Graphviz, graphviz } from 'd3-graphviz'; import type { BaseType, Selection } from 'd3-selection'; import { reaction, type IReactionDisposer } from 'mobx'; import { observer } from 'mobx-react-lite'; -import { useCallback, useRef } from 'react'; +import { useCallback, useRef, useState } from 'react'; import getLogger from '../utils/getLogger'; @@ -44,6 +44,7 @@ function DotGraphVisualizer({ const graphvizRef = useRef< Graphviz | undefined >(); + const [animate, setAnimate] = useState(true); const setElement = useCallback( (element: HTMLDivElement | null) => { @@ -72,15 +73,20 @@ function DotGraphVisualizer({ renderer.tweenPrecision('5%'); renderer.tweenShapes(false); renderer.convertEqualSidedPolygons(false); - const transition = () => - d3.transition().duration(transitionTimeOrDefault).ease(d3.easeCubic); - /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument, - @typescript-eslint/no-explicit-any -- - Workaround for error in `@types/d3-graphviz`. - */ - renderer.transition(transition as any); - let animate = true; - let previousSize = 0; + if (animate) { + const transition = () => + d3 + .transition() + .duration(transitionTimeOrDefault) + .ease(d3.easeCubic); + /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument, + @typescript-eslint/no-explicit-any -- + Workaround for error in `@types/d3-graphviz`. + */ + renderer.transition(transition as any); + } else { + renderer.tweenPaths(false); + } let newViewBox = { width: 0, height: 0 }; renderer.onerror(LOG.error.bind(LOG)); renderer.on( @@ -108,7 +114,11 @@ function DotGraphVisualizer({ d3.select(element).selectAll('title').remove(); }); if (fitZoom !== undefined) { - renderer.on('transitionStart', () => fitZoom(newViewBox)); + if (animate) { + renderer.on('transitionStart', () => fitZoom(newViewBox)); + } else { + renderer.on('end', () => fitZoom(false)); + } } disposerRef.current = reaction( () => dotSource(graph), @@ -119,20 +129,25 @@ function DotGraphVisualizer({ const [source, size] = result; // Disable tweening for large graphs to improve performance. // See https://github.com/magjac/d3-graphviz/issues/232#issuecomment-1157555213 - const newAnimate = size + previousSize < animateThresholdOrDefault; - if (animate !== newAnimate) { - renderer.tweenPaths(newAnimate); - animate = newAnimate; + const newAnimate = size < animateThresholdOrDefault; + if (animate === newAnimate) { + renderer.renderDot(source); + } else { + setAnimate(newAnimate); } - previousSize = size; - renderer.renderDot(source); }, { fireImmediately: true }, ); graphvizRef.current = renderer; } }, - [graph, fitZoom, transitionTimeOrDefault, animateThresholdOrDefault], + [ + graph, + fitZoom, + transitionTimeOrDefault, + animateThresholdOrDefault, + animate, + ], ); return ; @@ -141,7 +156,7 @@ function DotGraphVisualizer({ DotGraphVisualizer.defaultProps = { fitZoom: undefined, transitionTime: 250, - animateThreshold: 50, + animateThreshold: 100, }; export default observer(DotGraphVisualizer); diff --git a/subprojects/frontend/src/graph/ZoomCanvas.tsx b/subprojects/frontend/src/graph/ZoomCanvas.tsx index 2bb7f139..0254bc59 100644 --- a/subprojects/frontend/src/graph/ZoomCanvas.tsx +++ b/subprojects/frontend/src/graph/ZoomCanvas.tsx @@ -34,10 +34,11 @@ export type ChangeZoomCallback = (factor: number) => void; export type SetFitZoomCallback = (fitZoom: boolean) => void; -export type FitZoomCallback = (newSize?: { +export type FitZoomCallback = ((newSize?: { width: number; height: number; -}) => void; +}) => void) & + ((newSize: boolean) => void); export default function ZoomCanvas({ children, @@ -79,7 +80,7 @@ export default function ZoomCanvas({ } let width = 0; let height = 0; - if (newSize === undefined) { + if (newSize === undefined || typeof newSize === 'boolean') { const elementRect = elementRef.current.getBoundingClientRect(); const currentFactor = d3.zoomTransform(canvasRef.current).k; width = elementRect.width / currentFactor; @@ -96,8 +97,11 @@ export default function ZoomCanvas({ (canvasRect.width - 2 * fitPaddingOrDefault) / width, (canvasRect.height - 2 * fitPaddingOrDefault) / height, ); - const zoomTransition = makeTransition(canvasRef.current); - zoomRef.current.transform(zoomTransition, d3.zoomIdentity.scale(factor)); + const target = + newSize === false + ? d3.select(canvasRef.current) + : makeTransition(canvasRef.current); + zoomRef.current.transform(target, d3.zoomIdentity.scale(factor)); }, [fitPaddingOrDefault, makeTransition], ); -- cgit v1.2.3-54-g00ecf