diff options
Diffstat (limited to 'subprojects/frontend/src/graph/GraphArea.tsx')
-rw-r--r-- | subprojects/frontend/src/graph/GraphArea.tsx | 83 |
1 files changed, 27 insertions, 56 deletions
diff --git a/subprojects/frontend/src/graph/GraphArea.tsx b/subprojects/frontend/src/graph/GraphArea.tsx index b55245d8..1da7eaef 100644 --- a/subprojects/frontend/src/graph/GraphArea.tsx +++ b/subprojects/frontend/src/graph/GraphArea.tsx | |||
@@ -8,9 +8,9 @@ import Box from '@mui/material/Box'; | |||
8 | import * as d3 from 'd3'; | 8 | import * as d3 from 'd3'; |
9 | import { type Graphviz, graphviz } from 'd3-graphviz'; | 9 | import { type Graphviz, graphviz } from 'd3-graphviz'; |
10 | import type { BaseType, Selection } from 'd3-selection'; | 10 | import type { BaseType, Selection } from 'd3-selection'; |
11 | import { zoom as d3Zoom } from 'd3-zoom'; | ||
11 | import { reaction, type IReactionDisposer } from 'mobx'; | 12 | import { reaction, type IReactionDisposer } from 'mobx'; |
12 | import { useCallback, useRef, useState } from 'react'; | 13 | import { useCallback, useRef, useState } from 'react'; |
13 | import { useResizeDetector } from 'react-resize-detector'; | ||
14 | 14 | ||
15 | import { useRootStore } from '../RootStoreProvider'; | 15 | import { useRootStore } from '../RootStoreProvider'; |
16 | import type { SemanticsSuccessResult } from '../xtext/xtextServiceResults'; | 16 | import type { SemanticsSuccessResult } from '../xtext/xtextServiceResults'; |
@@ -88,59 +88,30 @@ export default function GraphArea(): JSX.Element { | |||
88 | d3.ZoomBehavior<HTMLDivElement, unknown> | undefined | 88 | d3.ZoomBehavior<HTMLDivElement, unknown> | undefined |
89 | >(); | 89 | >(); |
90 | const [zoom, setZoom] = useState<Transform>({ x: 0, y: 0, k: 1 }); | 90 | const [zoom, setZoom] = useState<Transform>({ x: 0, y: 0, k: 1 }); |
91 | const widthRef = useRef<number | undefined>(); | ||
92 | const heightRef = useRef<number | undefined>(); | ||
93 | 91 | ||
94 | const onResize = useCallback( | 92 | const setCanvas = useCallback((element: HTMLDivElement | null) => { |
95 | (width: number | undefined, height: number | undefined) => { | 93 | canvasRef.current = element ?? undefined; |
96 | if (canvasRef.current === undefined || zoomRef.current === undefined) { | 94 | if (element === null) { |
97 | return; | 95 | return; |
98 | } | 96 | } |
99 | let moveX = 0; | 97 | const zoomBehavior = d3Zoom<HTMLDivElement, unknown>(); |
100 | let moveY = 0; | 98 | // `@types/d3-zoom` does not contain the `center` function, because it is |
101 | if (widthRef.current !== undefined && width !== undefined) { | 99 | // only available as a pull request for `d3-zoom`. |
102 | moveX = (width - widthRef.current) / 2; | 100 | ( |
103 | } | 101 | zoomBehavior as unknown as { |
104 | if (heightRef.current !== undefined && height !== undefined) { | 102 | center(callback: (event: MouseEvent) => [number, number]): unknown; |
105 | moveY = (height - heightRef.current) / 2; | ||
106 | } | ||
107 | widthRef.current = width; | ||
108 | heightRef.current = height; | ||
109 | if (moveX === 0 && moveY === 0) { | ||
110 | return; | ||
111 | } | ||
112 | const currentTransform = d3.zoomTransform(canvasRef.current); | ||
113 | zoomRef.current.translateBy( | ||
114 | d3.select(canvasRef.current), | ||
115 | moveX / currentTransform.k - moveX, | ||
116 | moveY / currentTransform.k - moveY, | ||
117 | ); | ||
118 | }, | ||
119 | [], | ||
120 | ); | ||
121 | |||
122 | const { ref: setCanvasResize } = useResizeDetector({ | ||
123 | onResize, | ||
124 | }); | ||
125 | |||
126 | const setCanvas = useCallback( | ||
127 | (element: HTMLDivElement | null) => { | ||
128 | canvasRef.current = element ?? undefined; | ||
129 | setCanvasResize(element); | ||
130 | if (element === null) { | ||
131 | return; | ||
132 | } | 103 | } |
133 | const zoomBehavior = d3.zoom<HTMLDivElement, unknown>(); | 104 | ).center((event: MouseEvent) => { |
134 | zoomBehavior.on( | 105 | const { width, height } = element.getBoundingClientRect(); |
135 | 'zoom', | 106 | const [x, y] = d3.pointer(event, element); |
136 | (event: d3.D3ZoomEvent<HTMLDivElement, unknown>) => | 107 | return [x - width / 2, y - height / 2]; |
137 | setZoom(event.transform), | 108 | }); |
138 | ); | 109 | zoomBehavior.on('zoom', (event: d3.D3ZoomEvent<HTMLDivElement, unknown>) => |
139 | d3.select(element).call(zoomBehavior); | 110 | setZoom(event.transform), |
140 | zoomRef.current = zoomBehavior; | 111 | ); |
141 | }, | 112 | d3.select(element).call(zoomBehavior); |
142 | [setCanvasResize], | 113 | zoomRef.current = zoomBehavior; |
143 | ); | 114 | }, []); |
144 | 115 | ||
145 | const setElement = useCallback( | 116 | const setElement = useCallback( |
146 | (element: HTMLDivElement | null) => { | 117 | (element: HTMLDivElement | null) => { |
@@ -210,8 +181,8 @@ export default function GraphArea(): JSX.Element { | |||
210 | rect.setAttribute('width', String(xmax - xmin)); | 181 | rect.setAttribute('width', String(xmax - xmin)); |
211 | rect.setAttribute('height', String(ymax - ymin)); | 182 | rect.setAttribute('height', String(ymax - ymin)); |
212 | rect.setAttribute('height', String(ymax - ymin)); | 183 | rect.setAttribute('height', String(ymax - ymin)); |
213 | rect.setAttribute('rx', '12'); | 184 | rect.setAttribute('rx', '8'); |
214 | rect.setAttribute('ry', '12'); | 185 | rect.setAttribute('ry', '8'); |
215 | node.replaceChild(rect, path); | 186 | node.replaceChild(rect, path); |
216 | }); | 187 | }); |
217 | }); | 188 | }); |
@@ -302,8 +273,8 @@ export default function GraphArea(): JSX.Element { | |||
302 | <Box | 273 | <Box |
303 | sx={{ | 274 | sx={{ |
304 | position: 'absolute', | 275 | position: 'absolute', |
305 | top: `${50 * zoom.k}%`, | 276 | top: '50%', |
306 | left: `${50 * zoom.k}%`, | 277 | left: '50%', |
307 | transform: ` | 278 | transform: ` |
308 | translate(${zoom.x}px, ${zoom.y}px) | 279 | translate(${zoom.x}px, ${zoom.y}px) |
309 | scale(${zoom.k}) | 280 | scale(${zoom.k}) |