Skip to content

Commit d25b515

Browse files
committed
Reapply "fFIx zoom/panning of SVG."
This reverts commit 6e65d733e71fef7ce31a55273315b5d32e6bac94.
1 parent 86068f9 commit d25b515

File tree

1 file changed

+116
-1
lines changed

1 file changed

+116
-1
lines changed

src/pages/status/migration/index.jsx

Lines changed: 116 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,8 +277,112 @@ function Filters({ counts, filters, onFilter }) {
277277

278278
function Graph(props) {
279279
const [error, setState] = useState("");
280+
const containerRef = React.useRef(null);
280281
const url = urls.migrations.graph.replace("<NAME>", props.children);
281282
const onError = (error) => setState(error);
283+
284+
useEffect(() => {
285+
if (!containerRef.current || error) return;
286+
287+
const container = d3.select(containerRef.current);
288+
let timer = null;
289+
290+
const setupZoom = () => {
291+
const svgElement = container.select('svg').node();
292+
if (!svgElement) {
293+
// Wait a bit for SVG to load
294+
timer = setTimeout(setupZoom, 100);
295+
return;
296+
}
297+
298+
const svg = d3.select(svgElement);
299+
300+
// Check if group already exists
301+
let svgGroup = svg.select('g.zoom-group');
302+
if (svgGroup.empty()) {
303+
svgGroup = svg.append('g').attr('class', 'zoom-group');
304+
305+
// Move all existing children into the group (except the group itself)
306+
svg.selectAll('*').each(function() {
307+
const node = this;
308+
if (node !== svgGroup.node() && node.parentNode === svgElement) {
309+
svgGroup.node().appendChild(node);
310+
}
311+
});
312+
}
313+
314+
// Get SVG dimensions (use viewBox if available, otherwise use bounding rect)
315+
const viewBox = svgElement.viewBox?.baseVal;
316+
const svgWidth = viewBox ? viewBox.width : (svgElement.getBoundingClientRect().width || containerRef.current.clientWidth);
317+
const svgHeight = viewBox ? viewBox.height : (svgElement.getBoundingClientRect().height || containerRef.current.clientHeight || 600);
318+
319+
// Get bounding box of the content (relative to SVG coordinate system)
320+
const bbox = svgElement.getBBox();
321+
322+
// Calculate initial transform values
323+
const initialScale = Math.min(
324+
svgWidth / bbox.width,
325+
svgHeight / bbox.height,
326+
1
327+
) * 0.9;
328+
329+
// Calculate center position in SVG coordinate system
330+
const centerX = svgWidth / 2;
331+
const centerY = svgHeight / 2;
332+
333+
// The bbox center in SVG coordinates
334+
const bboxCenterX = bbox.x + bbox.width / 2;
335+
const bboxCenterY = bbox.y + bbox.height / 2;
336+
337+
// Translate so that the bbox center (scaled) maps to the SVG center
338+
const initialTranslate = [
339+
centerX - bboxCenterX * initialScale,
340+
centerY - bboxCenterY * initialScale,
341+
];
342+
343+
// Store initial transform for reset
344+
const initialTransform = d3.zoomIdentity
345+
.translate(initialTranslate[0], initialTranslate[1])
346+
.scale(initialScale);
347+
348+
// Set up zoom behavior - apply to SVG element for proper drag sensitivity
349+
const zoom = d3.zoom()
350+
.scaleExtent([0.1, 4])
351+
.on("zoom", (event) => {
352+
svgGroup.attr("transform", event.transform);
353+
});
354+
355+
// Apply zoom to the SVG element itself for proper drag behavior
356+
svg.call(zoom);
357+
358+
// Center and scale initially (only if not already transformed)
359+
if (!svgGroup.attr("transform")) {
360+
svg.call(zoom.transform, initialTransform);
361+
}
362+
363+
// Double-click to reset zoom/pan to initial state
364+
svg.on("dblclick.zoom", null); // Remove default double-click zoom
365+
svg.on("dblclick", function() {
366+
svg.transition()
367+
.duration(750)
368+
.call(zoom.transform, initialTransform);
369+
});
370+
};
371+
372+
setupZoom();
373+
374+
// Cleanup
375+
return () => {
376+
if (timer) clearTimeout(timer);
377+
const svgElement = container.select('svg').node();
378+
if (svgElement) {
379+
const svg = d3.select(svgElement);
380+
svg.on(".zoom", null);
381+
svg.on("dblclick", null);
382+
}
383+
};
384+
}, [error, url]);
385+
282386
return (
283387
<div>
284388
<p style={{textAlign: "center"}}>
@@ -291,12 +395,23 @@ function Graph(props) {
291395
<p style={{textAlign: "center"}}>
292396
Graph is unavailable.
293397
</p> :
294-
<div style={{ overflowX: "auto" }}>
398+
<div
399+
ref={containerRef}
400+
style={{
401+
width: "100%",
402+
height: "600px",
403+
overflow: "hidden",
404+
border: "1px solid var(--ifm-color-emphasis-300)",
405+
borderRadius: "8px",
406+
cursor: "move"
407+
}}
408+
>
295409
<SVG
296410
onError={onError}
297411
src={url}
298412
title={props.children}
299413
description={`Migration graph for ${props.children}`}
414+
style={{ width: "100%", height: "100%" }}
300415
/>
301416
</div>
302417
}

0 commit comments

Comments
 (0)