This is an automated email from the ASF dual-hosted git repository. zqr10159 pushed a commit to branch 2.0.0 in repository https://gitbox.apache.org/repos/asf/hertzbeat.git
commit 202f407d2c094401fc42934adeafee3cf02618cc Author: Logic <[email protected]> AuthorDate: Fri May 29 21:33:24 2026 +0800 fix(web-next): stop topology first-paint overfit --- .../packages/hertzbeat-ui/src/topology-g6.test.tsx | 22 ++++++++++++++++------ web-next/packages/hertzbeat-ui/src/topology-g6.tsx | 12 +++++++----- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/web-next/packages/hertzbeat-ui/src/topology-g6.test.tsx b/web-next/packages/hertzbeat-ui/src/topology-g6.test.tsx index 9e4bf33697..11ebfd8f5d 100644 --- a/web-next/packages/hertzbeat-ui/src/topology-g6.test.tsx +++ b/web-next/packages/hertzbeat-ui/src/topology-g6.test.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { readFileSync } from 'node:fs'; import { renderToStaticMarkup } from 'react-dom/server'; import { describe, expect, it } from 'vitest'; import { @@ -18,6 +19,8 @@ import { HzTopologyG6Canvas } from './topology-g6'; +const topologyG6Source = readFileSync(new URL('./topology-g6.tsx', import.meta.url), 'utf8'); + describe('@hertzbeat/ui topology G6 canvas', () => { const graph = { nodes: [ @@ -351,30 +354,37 @@ describe('@hertzbeat/ui topology G6 canvas', () => { expect(html).toContain('data-topology-node-focus-href="/topology?entityId=501&depth=1"'); }); - it('schedules a post-render fit view pass so the real graph is framed on first paint', () => { - const source = String(HzTopologyG6Canvas); + it('schedules a post-render overflow-only fit pass so small graphs are not magnified on first paint', () => { + const source = topologyG6Source; const html = renderToStaticMarkup(<HzTopologyG6Canvas graph={graph} />); expect(source).toContain('scheduleInitialFitView'); expect(source).toContain('fitAndCenterG6Viewport'); + expect(source).toContain("fitAndCenterG6Viewport(runtimeGraph, { when: 'overflow' }, false)"); + expect(source).toContain("fitAndCenterG6Viewport(runtimeGraph, { when: 'overflow' }, { duration: 120 })"); + expect(source).not.toContain("fitAndCenterG6Viewport(runtimeGraph, { when: 'always' }, false)"); + expect(source).not.toContain("fitAndCenterG6Viewport(runtimeGraph, { when: 'always' }, { duration: 120 })"); expect(source).not.toContain("runtimeGraph.fitView?.({ when: 'always' }, false);\n await runtimeGraph.fitCenter?.(false);"); expect(html).toContain('data-hz-topology-g6-auto-fit-zoom-bounds="0.18-1.35"'); expect(html).toContain('data-hz-topology-g6-auto-fit-max-zoom="1.35"'); + expect(html).toContain('data-hz-topology-g6-auto-fit-growth="no-magnify-small-graphs"'); + expect(html).toContain('data-hz-topology-g6-fit-mode="overflow-only-center"'); expect(clampHzTopologyG6AutoFitZoom(4.8)).toBe(HZ_TOPOLOGY_G6_AUTO_FIT_MAX_ZOOM); expect(clampHzTopologyG6AutoFitZoom(0.72)).toBe(0.72); }); it('centers the shared G6 canvas after fit and reset view actions', () => { - const source = String(HzTopologyG6Canvas); + const source = topologyG6Source; const html = renderToStaticMarkup(<HzTopologyG6Canvas graph={buildHzTopologyG6ScaleFixture(8)} />); expect(source).toContain('centerGraphView'); expect(source).toContain('centerGraphView("fit-view")'); expect(source).toContain('centerGraphView("reset-view")'); - expect(source).toContain('fitAndCenterG6Viewport(graphRef.current, { when: "always" }, { duration: 180 })'); + expect(source).toContain("fitAndCenterG6Viewport(graphRef.current, { when: 'overflow' }, { duration: 180 })"); + expect(source).not.toContain("fitAndCenterG6Viewport(graphRef.current, { when: 'always' }, { duration: 180 })"); expect(html).toContain('data-hz-topology-g6-viewport-owner="hertzbeat-ui-g6-viewport"'); - expect(html).toContain('data-hz-topology-g6-fit-behavior="fit-and-center"'); - expect(html).toContain('data-hz-topology-g6-reset-behavior="zoom-fit-center"'); + expect(html).toContain('data-hz-topology-g6-fit-behavior="overflow-fit-and-center"'); + expect(html).toContain('data-hz-topology-g6-reset-behavior="zoom-one-overflow-fit-center"'); }); it('preserves operator wheel and pan zoom while hover or selection styling updates redraw the G6 graph', () => { diff --git a/web-next/packages/hertzbeat-ui/src/topology-g6.tsx b/web-next/packages/hertzbeat-ui/src/topology-g6.tsx index 1f2a102286..57fe3556f6 100644 --- a/web-next/packages/hertzbeat-ui/src/topology-g6.tsx +++ b/web-next/packages/hertzbeat-ui/src/topology-g6.tsx @@ -1141,7 +1141,7 @@ async function fitAndCenterG6Viewport( function scheduleInitialFitView(runtimeGraph: G6GraphRuntime, shouldRun: () => boolean) { return window.setTimeout(() => { if (!shouldRun()) return; - void fitAndCenterG6Viewport(runtimeGraph, { when: 'always' }, { duration: 120 }); + void fitAndCenterG6Viewport(runtimeGraph, { when: 'overflow' }, { duration: 120 }); }, 180); } @@ -1592,7 +1592,7 @@ export function HzTopologyG6Canvas({ clearSharedHover(); }); await runtimeGraph.render(); - await fitAndCenterG6Viewport(runtimeGraph, { when: 'always' }, false); + await fitAndCenterG6Viewport(runtimeGraph, { when: 'overflow' }, false); lastFitStructureKeyRef.current = graphStructureKey; lastDrawGraphKeyRef.current = latestG6RenderKeyRef.current; initialFitTimerRef.current = scheduleInitialFitView(runtimeGraph, () => !hasUserViewportInteractedRef.current); @@ -1698,7 +1698,7 @@ export function HzTopologyG6Canvas({ const centerGraphView = React.useCallback(async (action: 'fit-view' | 'reset-view') => { if (action === 'reset-view') await graphRef.current?.zoomTo?.(1, { duration: 180 }); - await fitAndCenterG6Viewport(graphRef.current, { when: 'always' }, { duration: 180 }); + await fitAndCenterG6Viewport(graphRef.current, { when: 'overflow' }, { duration: 180 }); }, []); const fitView = React.useCallback(() => { @@ -1830,6 +1830,8 @@ export function HzTopologyG6Canvas({ data-hz-topology-g6-auto-fit-owner="hertzbeat-ui-g6-auto-fit" data-hz-topology-g6-auto-fit-zoom-bounds={`${HZ_TOPOLOGY_G6_MIN_ZOOM}-${HZ_TOPOLOGY_G6_AUTO_FIT_MAX_ZOOM}`} data-hz-topology-g6-auto-fit-max-zoom={HZ_TOPOLOGY_G6_AUTO_FIT_MAX_ZOOM} + data-hz-topology-g6-auto-fit-growth="no-magnify-small-graphs" + data-hz-topology-g6-fit-mode="overflow-only-center" data-hz-topology-g6-viewport-interaction-state={viewportInteractionState} data-hz-topology-g6-viewport-interaction-owner="hertzbeat-ui-g6-viewport-interaction" data-hz-topology-g6-viewport-preservation="clamped-wheel-pan-zoom" @@ -1877,8 +1879,8 @@ export function HzTopologyG6Canvas({ data-hz-topology-g6-style-redraw-behavior="no-auto-fit" data-hz-topology-g6-style-redraw-skip="identical-render-key" data-hz-topology-g6-blank-hover-clear="no-op-without-hover" - data-hz-topology-g6-fit-behavior="fit-and-center" - data-hz-topology-g6-reset-behavior="zoom-fit-center" + data-hz-topology-g6-fit-behavior="overflow-fit-and-center" + data-hz-topology-g6-reset-behavior="zoom-one-overflow-fit-center" data-hz-topology-g6-node-selection={selectionMode} data-hz-topology-g6-edge-selection={selectionMode} data-hz-topology-g6-edge-hit-target="wide-pointer-band" --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
