diff --git a/frontend/src/GraphView.jsx b/frontend/src/GraphView.jsx index fa5cdac..1243e67 100644 --- a/frontend/src/GraphView.jsx +++ b/frontend/src/GraphView.jsx @@ -1,5 +1,6 @@ import React, { useState, useRef, useEffect } from 'react'; import * as d3 from 'd3'; +import { IconZoomIn, IconZoomOut, IconMaximize } from '@tabler/icons-react'; function GraphView({ servers, connections }) { const [nodePositions, setNodePositions] = useState({}); @@ -69,13 +70,44 @@ function GraphView({ servers, connections }) { const g = d3.select(gRef.current); zoomRef.current = d3 .zoom() - .scaleExtent([0.5, 2]) + .scaleExtent([0.3, 3]) .on('zoom', (event) => { g.attr('transform', event.transform); }); svg.call(zoomRef.current); }, []); + // Контролы масштабирования + const zoomBy = (factor) => { + const svg = d3.select(svgRef.current); + svg.transition().duration(200).call(zoomRef.current.scaleBy, factor); + }; + + const fitToView = () => { + const svgEl = svgRef.current; + if (!svgEl) return; + const box = svgEl.getBoundingClientRect(); + const padding = 40; + const positions = nodePositions; + const ids = Object.keys(positions); + if (ids.length === 0) return; + let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity; + ids.forEach((id) => { + const p = positions[id]; + minX = Math.min(minX, p.x - NODE_WIDTH / 2); + minY = Math.min(minY, p.y - NODE_HEIGHT / 2); + maxX = Math.max(maxX, p.x + NODE_WIDTH / 2); + maxY = Math.max(maxY, p.y + NODE_HEIGHT / 2); + }); + const contentW = Math.max(1, maxX - minX); + const contentH = Math.max(1, maxY - minY); + const scale = Math.max(0.5, Math.min(1.6, Math.min((box.width - padding) / contentW, (box.height - padding) / contentH))); + const tx = (box.width - scale * (minX + maxX)) / 2; + const ty = (box.height - scale * (minY + maxY)) / 2; + const t = d3.zoomIdentity.translate(tx, ty).scale(scale); + d3.select(svgEl).transition().duration(250).call(zoomRef.current.transform, t); + }; + if (servers.length === 0) { return (