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&amp;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]

Reply via email to