feat: Enhance GraphView with improved zoom functionality and label styling, including shadows for better visibility and aesthetics
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 5m48s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 5m48s
This commit is contained in:
+59
-31
@@ -79,8 +79,11 @@ function GraphView({ servers, connections }) {
|
|||||||
|
|
||||||
// Контролы масштабирования
|
// Контролы масштабирования
|
||||||
const zoomBy = (factor) => {
|
const zoomBy = (factor) => {
|
||||||
|
if (!zoomRef.current || !svgRef.current) return;
|
||||||
const svg = d3.select(svgRef.current);
|
const svg = d3.select(svgRef.current);
|
||||||
svg.transition().duration(200).call(zoomRef.current.scaleBy, factor);
|
const node = svgRef.current;
|
||||||
|
const center = [node.clientWidth / 2, node.clientHeight / 2];
|
||||||
|
svg.transition().duration(220).call(zoomRef.current.scaleBy, factor, center);
|
||||||
};
|
};
|
||||||
|
|
||||||
const fitToView = () => {
|
const fitToView = () => {
|
||||||
@@ -263,6 +266,10 @@ function GraphView({ servers, connections }) {
|
|||||||
<filter id="cardShadow" x="-50%" y="-50%" width="200%" height="200%">
|
<filter id="cardShadow" x="-50%" y="-50%" width="200%" height="200%">
|
||||||
<feDropShadow dx="0" dy="2" stdDeviation="3" floodOpacity="0.2" />
|
<feDropShadow dx="0" dy="2" stdDeviation="3" floodOpacity="0.2" />
|
||||||
</filter>
|
</filter>
|
||||||
|
{/* Тень для ярлыков ссылок */}
|
||||||
|
<filter id="labelShadow" x="-50%" y="-50%" width="200%" height="200%">
|
||||||
|
<feDropShadow dx="0" dy="1" stdDeviation="2" floodOpacity="0.25" />
|
||||||
|
</filter>
|
||||||
</defs>
|
</defs>
|
||||||
{/* Подложка сетки */}
|
{/* Подложка сетки */}
|
||||||
<rect width="100%" height="100%" fill="url(#grid)" />
|
<rect width="100%" height="100%" fill="url(#grid)" />
|
||||||
@@ -309,36 +316,57 @@ function GraphView({ servers, connections }) {
|
|||||||
style={{ vectorEffect: 'non-scaling-stroke' }}
|
style={{ vectorEffect: 'non-scaling-stroke' }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Подпись связи */}
|
{/* Подпись связи: ярлык с подложкой и halo */}
|
||||||
<rect
|
{(() => {
|
||||||
x={mx - 50}
|
const labelW = 140;
|
||||||
y={my - 35}
|
const labelH = 38;
|
||||||
width="80"
|
const lx = cx - labelW / 2;
|
||||||
height="30"
|
const ly = cy - labelH / 2 - 4; // слегка выше вершины
|
||||||
rx="15"
|
return (
|
||||||
fill="#ffffff"
|
<g style={{ pointerEvents: 'none' }}>
|
||||||
stroke={style.color}
|
<rect
|
||||||
strokeWidth="1"
|
x={lx}
|
||||||
/>
|
y={ly}
|
||||||
<text
|
width={labelW}
|
||||||
x={mx - 10}
|
height={labelH}
|
||||||
y={my - 18}
|
rx="16"
|
||||||
textAnchor="middle"
|
fill="#ffffff"
|
||||||
fontSize="11"
|
stroke={style.color}
|
||||||
fill="#374151"
|
strokeWidth="1"
|
||||||
fontWeight="bold"
|
filter="url(#labelShadow)"
|
||||||
>
|
/>
|
||||||
{connection.tunnelType}
|
{/* Halo для заголовка */}
|
||||||
</text>
|
<text
|
||||||
<text
|
x={cx}
|
||||||
x={mx}
|
y={ly + 16}
|
||||||
y={my - 3}
|
textAnchor="middle"
|
||||||
textAnchor="middle"
|
fontSize="12"
|
||||||
fontSize="9"
|
fontWeight="700"
|
||||||
fill="#6b7280"
|
stroke="#ffffff"
|
||||||
>
|
strokeWidth="3"
|
||||||
{connection.ipA} ⇄ {connection.ipB}
|
strokeLinejoin="round"
|
||||||
</text>
|
fill="#374151"
|
||||||
|
style={{ paintOrder: 'stroke fill' }}
|
||||||
|
>
|
||||||
|
{connection.tunnelType}
|
||||||
|
</text>
|
||||||
|
{/* Линия IP с более мелким шрифтом */}
|
||||||
|
<text
|
||||||
|
x={cx}
|
||||||
|
y={ly + 30}
|
||||||
|
textAnchor="middle"
|
||||||
|
fontSize="10"
|
||||||
|
fill="#6b7280"
|
||||||
|
stroke="#ffffff"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
style={{ paintOrder: 'stroke fill' }}
|
||||||
|
>
|
||||||
|
{connection.ipA} ⇄ {connection.ipB}
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
</g>
|
</g>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
Reference in New Issue
Block a user