diff options
-rw-r--r-- | subprojects/frontend/src/graph/DotGraphVisualizer.tsx | 53 | ||||
-rw-r--r-- | subprojects/frontend/src/graph/ZoomCanvas.tsx | 14 |
2 files changed, 43 insertions, 24 deletions
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'; | |||
9 | import type { BaseType, Selection } from 'd3-selection'; | 9 | import type { BaseType, Selection } from 'd3-selection'; |
10 | import { reaction, type IReactionDisposer } from 'mobx'; | 10 | import { reaction, type IReactionDisposer } from 'mobx'; |
11 | import { observer } from 'mobx-react-lite'; | 11 | import { observer } from 'mobx-react-lite'; |
12 | import { useCallback, useRef } from 'react'; | 12 | import { useCallback, useRef, useState } from 'react'; |
13 | 13 | ||
14 | import getLogger from '../utils/getLogger'; | 14 | import getLogger from '../utils/getLogger'; |
15 | 15 | ||
@@ -44,6 +44,7 @@ function DotGraphVisualizer({ | |||
44 | const graphvizRef = useRef< | 44 | const graphvizRef = useRef< |
45 | Graphviz<BaseType, unknown, null, undefined> | undefined | 45 | Graphviz<BaseType, unknown, null, undefined> | undefined |
46 | >(); | 46 | >(); |
47 | const [animate, setAnimate] = useState(true); | ||
47 | 48 | ||
48 | const setElement = useCallback( | 49 | const setElement = useCallback( |
49 | (element: HTMLDivElement | null) => { | 50 | (element: HTMLDivElement | null) => { |
@@ -72,15 +73,20 @@ function DotGraphVisualizer({ | |||
72 | renderer.tweenPrecision('5%'); | 73 | renderer.tweenPrecision('5%'); |
73 | renderer.tweenShapes(false); | 74 | renderer.tweenShapes(false); |
74 | renderer.convertEqualSidedPolygons(false); | 75 | renderer.convertEqualSidedPolygons(false); |
75 | const transition = () => | 76 | if (animate) { |
76 | d3.transition().duration(transitionTimeOrDefault).ease(d3.easeCubic); | 77 | const transition = () => |
77 | /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument, | 78 | d3 |
78 | @typescript-eslint/no-explicit-any -- | 79 | .transition() |
79 | Workaround for error in `@types/d3-graphviz`. | 80 | .duration(transitionTimeOrDefault) |
80 | */ | 81 | .ease(d3.easeCubic); |
81 | renderer.transition(transition as any); | 82 | /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument, |
82 | let animate = true; | 83 | @typescript-eslint/no-explicit-any -- |
83 | let previousSize = 0; | 84 | Workaround for error in `@types/d3-graphviz`. |
85 | */ | ||
86 | renderer.transition(transition as any); | ||
87 | } else { | ||
88 | renderer.tweenPaths(false); | ||
89 | } | ||
84 | let newViewBox = { width: 0, height: 0 }; | 90 | let newViewBox = { width: 0, height: 0 }; |
85 | renderer.onerror(LOG.error.bind(LOG)); | 91 | renderer.onerror(LOG.error.bind(LOG)); |
86 | renderer.on( | 92 | renderer.on( |
@@ -108,7 +114,11 @@ function DotGraphVisualizer({ | |||
108 | d3.select(element).selectAll('title').remove(); | 114 | d3.select(element).selectAll('title').remove(); |
109 | }); | 115 | }); |
110 | if (fitZoom !== undefined) { | 116 | if (fitZoom !== undefined) { |
111 | renderer.on('transitionStart', () => fitZoom(newViewBox)); | 117 | if (animate) { |
118 | renderer.on('transitionStart', () => fitZoom(newViewBox)); | ||
119 | } else { | ||
120 | renderer.on('end', () => fitZoom(false)); | ||
121 | } | ||
112 | } | 122 | } |
113 | disposerRef.current = reaction( | 123 | disposerRef.current = reaction( |
114 | () => dotSource(graph), | 124 | () => dotSource(graph), |
@@ -119,20 +129,25 @@ function DotGraphVisualizer({ | |||
119 | const [source, size] = result; | 129 | const [source, size] = result; |
120 | // Disable tweening for large graphs to improve performance. | 130 | // Disable tweening for large graphs to improve performance. |
121 | // See https://github.com/magjac/d3-graphviz/issues/232#issuecomment-1157555213 | 131 | // See https://github.com/magjac/d3-graphviz/issues/232#issuecomment-1157555213 |
122 | const newAnimate = size + previousSize < animateThresholdOrDefault; | 132 | const newAnimate = size < animateThresholdOrDefault; |
123 | if (animate !== newAnimate) { | 133 | if (animate === newAnimate) { |
124 | renderer.tweenPaths(newAnimate); | 134 | renderer.renderDot(source); |
125 | animate = newAnimate; | 135 | } else { |
136 | setAnimate(newAnimate); | ||
126 | } | 137 | } |
127 | previousSize = size; | ||
128 | renderer.renderDot(source); | ||
129 | }, | 138 | }, |
130 | { fireImmediately: true }, | 139 | { fireImmediately: true }, |
131 | ); | 140 | ); |
132 | graphvizRef.current = renderer; | 141 | graphvizRef.current = renderer; |
133 | } | 142 | } |
134 | }, | 143 | }, |
135 | [graph, fitZoom, transitionTimeOrDefault, animateThresholdOrDefault], | 144 | [ |
145 | graph, | ||
146 | fitZoom, | ||
147 | transitionTimeOrDefault, | ||
148 | animateThresholdOrDefault, | ||
149 | animate, | ||
150 | ], | ||
136 | ); | 151 | ); |
137 | 152 | ||
138 | return <GraphTheme ref={setElement} />; | 153 | return <GraphTheme ref={setElement} />; |
@@ -141,7 +156,7 @@ function DotGraphVisualizer({ | |||
141 | DotGraphVisualizer.defaultProps = { | 156 | DotGraphVisualizer.defaultProps = { |
142 | fitZoom: undefined, | 157 | fitZoom: undefined, |
143 | transitionTime: 250, | 158 | transitionTime: 250, |
144 | animateThreshold: 50, | 159 | animateThreshold: 100, |
145 | }; | 160 | }; |
146 | 161 | ||
147 | export default observer(DotGraphVisualizer); | 162 | 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; | |||
34 | 34 | ||
35 | export type SetFitZoomCallback = (fitZoom: boolean) => void; | 35 | export type SetFitZoomCallback = (fitZoom: boolean) => void; |
36 | 36 | ||
37 | export type FitZoomCallback = (newSize?: { | 37 | export type FitZoomCallback = ((newSize?: { |
38 | width: number; | 38 | width: number; |
39 | height: number; | 39 | height: number; |
40 | }) => void; | 40 | }) => void) & |
41 | ((newSize: boolean) => void); | ||
41 | 42 | ||
42 | export default function ZoomCanvas({ | 43 | export default function ZoomCanvas({ |
43 | children, | 44 | children, |
@@ -79,7 +80,7 @@ export default function ZoomCanvas({ | |||
79 | } | 80 | } |
80 | let width = 0; | 81 | let width = 0; |
81 | let height = 0; | 82 | let height = 0; |
82 | if (newSize === undefined) { | 83 | if (newSize === undefined || typeof newSize === 'boolean') { |
83 | const elementRect = elementRef.current.getBoundingClientRect(); | 84 | const elementRect = elementRef.current.getBoundingClientRect(); |
84 | const currentFactor = d3.zoomTransform(canvasRef.current).k; | 85 | const currentFactor = d3.zoomTransform(canvasRef.current).k; |
85 | width = elementRect.width / currentFactor; | 86 | width = elementRect.width / currentFactor; |
@@ -96,8 +97,11 @@ export default function ZoomCanvas({ | |||
96 | (canvasRect.width - 2 * fitPaddingOrDefault) / width, | 97 | (canvasRect.width - 2 * fitPaddingOrDefault) / width, |
97 | (canvasRect.height - 2 * fitPaddingOrDefault) / height, | 98 | (canvasRect.height - 2 * fitPaddingOrDefault) / height, |
98 | ); | 99 | ); |
99 | const zoomTransition = makeTransition(canvasRef.current); | 100 | const target = |
100 | zoomRef.current.transform(zoomTransition, d3.zoomIdentity.scale(factor)); | 101 | newSize === false |
102 | ? d3.select(canvasRef.current) | ||
103 | : makeTransition(canvasRef.current); | ||
104 | zoomRef.current.transform(target, d3.zoomIdentity.scale(factor)); | ||
101 | }, | 105 | }, |
102 | [fitPaddingOrDefault, makeTransition], | 106 | [fitPaddingOrDefault, makeTransition], |
103 | ); | 107 | ); |