From 7c0b8e571a613173ed9cec6e87af469c6b141ca8 Mon Sep 17 00:00:00 2001 From: Fringg Date: Thu, 19 Mar 2026 09:18:11 +0300 Subject: [PATCH] fix: graph layout, node visibility and FA2 settings - Switch to CSS Grid layout for reliable content area height - Use inferSettings() for ForceAtlas2 auto-tuning based on graph structure - Enable linLogMode and adjustSizes for better node clustering - Fit camera after layout settles with animatedReset - Increase node sizes for better visibility (MIN 8, MAX 40) - Center controls at bottom, fix overlay positions --- src/pages/ReferralNetwork/ReferralNetwork.tsx | 26 +++++++++---------- .../components/NetworkGraph.tsx | 18 +++++++++---- src/pages/ReferralNetwork/utils.ts | 6 ++--- 3 files changed, 29 insertions(+), 21 deletions(-) diff --git a/src/pages/ReferralNetwork/ReferralNetwork.tsx b/src/pages/ReferralNetwork/ReferralNetwork.tsx index 848ba2c..4c0f98a 100644 --- a/src/pages/ReferralNetwork/ReferralNetwork.tsx +++ b/src/pages/ReferralNetwork/ReferralNetwork.tsx @@ -31,10 +31,10 @@ export function ReferralNetwork() { return (
{/* Top bar */} -
+

@@ -45,11 +45,11 @@ export function ReferralNetwork() {

- {/* Main content area */} -
+ {/* Main content area — grid row takes all remaining space */} +
{/* Loading state */} {isLoading && ( -
+

{t('admin.referralNetwork.loading')}

@@ -59,35 +59,35 @@ export function ReferralNetwork() { {/* Error state */} {isError && ( -
+

{t('admin.referralNetwork.error')}

)} {/* Empty state */} {networkData && networkData.users.length === 0 && networkData.campaigns.length === 0 && ( -
+

{t('admin.referralNetwork.empty')}

)} - {/* Graph */} + {/* Graph — fills entire content area */} {networkData && (networkData.users.length > 0 || networkData.campaigns.length > 0) && ( <> - + {/* Bottom-left: stats overlay */} -
+
{/* Bottom-right: legend (hidden on mobile to avoid overlap) */} -
+
- {/* Bottom-right on mobile, bottom-center on desktop: controls */} -
+ {/* Bottom-center: controls */} +
diff --git a/src/pages/ReferralNetwork/components/NetworkGraph.tsx b/src/pages/ReferralNetwork/components/NetworkGraph.tsx index ff3c34a..f457cd0 100644 --- a/src/pages/ReferralNetwork/components/NetworkGraph.tsx +++ b/src/pages/ReferralNetwork/components/NetworkGraph.tsx @@ -2,6 +2,7 @@ import { useEffect, useRef, useCallback } from 'react'; import Graph from 'graphology'; import Sigma from 'sigma'; import FA2LayoutSupervisor from 'graphology-layout-forceatlas2/worker'; +import { inferSettings } from 'graphology-layout-forceatlas2'; import { useReferralNetworkStore } from '@/store/referralNetwork'; import type { NetworkGraphData, NetworkFilters } from '@/types/referralNetwork'; import { getUserNodeColor, getUserNodeSize, getCampaignColor } from '../utils'; @@ -246,24 +247,31 @@ export function NetworkGraph({ data, className }: NetworkGraphProps) { // Start ForceAtlas2 in a web worker (non-blocking) if (graph.order > 0) { + const inferred = inferSettings(graph); const supervisor = new FA2LayoutSupervisor(graph, { settings: { - gravity: 0.5, - scalingRatio: 10, - barnesHutOptimize: true, - slowDown: 2, + ...inferred, + linLogMode: true, + adjustSizes: true, + barnesHutOptimize: graph.order > 100, + slowDown: 5, }, }); fa2Ref.current = supervisor; supervisor.start(); - // Kill after a fixed duration to free the web worker thread + // Kill after a fixed duration, then fit camera to all nodes fa2TimerRef.current = setTimeout(() => { if (fa2Ref.current === supervisor) { supervisor.kill(); fa2Ref.current = null; } fa2TimerRef.current = null; + + // Fit camera to show all nodes after layout settles + if (sigmaRef.current) { + sigmaRef.current.getCamera().animatedReset({ duration: 400 }); + } }, FA2_DURATION_MS); } diff --git a/src/pages/ReferralNetwork/utils.ts b/src/pages/ReferralNetwork/utils.ts index 661e966..092e356 100644 --- a/src/pages/ReferralNetwork/utils.ts +++ b/src/pages/ReferralNetwork/utils.ts @@ -46,8 +46,8 @@ export function getUserNodeColor( * Size is proportional to direct_referrals, clamped between min and max. */ export function getUserNodeSize(directReferrals: number): number { - const MIN_SIZE = 4; - const MAX_SIZE = 30; + const MIN_SIZE = 8; + const MAX_SIZE = 40; if (directReferrals === 0) return MIN_SIZE; - return Math.min(MAX_SIZE, MIN_SIZE + Math.sqrt(directReferrals) * 4); + return Math.min(MAX_SIZE, MIN_SIZE + Math.sqrt(directReferrals) * 5); }