fix: referral network rendering — portal fix and visual tuning

Portal to document.body escapes main's contain:content which broke
fixed positioning (container had height:0 and was clipped to main).

Visual fixes for 662+ node graphs:
- labels only on large nodes (threshold 6→14, density 0.5→0.12)
- leaf nodes 5px, referrers 10px, campaigns 16px
- edge opacity reduced to prevent white starburst
- default edge color opacity lowered
This commit is contained in:
c0mrade
2026-03-19 11:12:05 +03:00
parent b787726d1b
commit 2b43a30ccc
3 changed files with 14 additions and 13 deletions

View File

@@ -1,3 +1,4 @@
import { createPortal } from 'react-dom';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { referralNetworkApi } from '@/api/referralNetwork'; import { referralNetworkApi } from '@/api/referralNetwork';
@@ -28,7 +29,7 @@ export function ReferralNetwork() {
const isPanelOpen = selectedNode !== null; const isPanelOpen = selectedNode !== null;
return ( return createPortal(
<div <div
id="referral-network-container" id="referral-network-container"
className="fixed inset-x-0 bottom-0 top-16 z-40 grid grid-rows-[auto_1fr] bg-[#0a0a0f] lg:top-14" className="fixed inset-x-0 bottom-0 top-16 z-40 grid grid-rows-[auto_1fr] bg-[#0a0a0f] lg:top-14"
@@ -103,6 +104,7 @@ export function ReferralNetwork() {
)} )}
</div> </div>
</div> </div>
</div> </div>,
document.body,
); );
} }

View File

@@ -21,7 +21,7 @@ function buildFullGraph(graphData: NetworkGraphData): Graph {
label: campaign.name, label: campaign.name,
x: Math.random() * 100, x: Math.random() * 100,
y: Math.random() * 100, y: Math.random() * 100,
size: 24, size: 16,
color: getCampaignColor(index), color: getCampaignColor(index),
type: 'circle', type: 'circle',
nodeType: 'campaign', nodeType: 'campaign',
@@ -53,8 +53,8 @@ function buildFullGraph(graphData: NetworkGraphData): Graph {
const edgeKey = `${edge.source}->${edge.target}`; const edgeKey = `${edge.source}->${edge.target}`;
if (!graph.hasEdge(edgeKey)) { if (!graph.hasEdge(edgeKey)) {
graph.addEdgeWithKey(edgeKey, edge.source, edge.target, { graph.addEdgeWithKey(edgeKey, edge.source, edge.target, {
color: edge.type === 'campaign' ? 'rgba(77, 217, 192, 0.12)' : 'rgba(255,255,255,0.06)', color: edge.type === 'campaign' ? 'rgba(77, 217, 192, 0.06)' : 'rgba(255,255,255,0.03)',
size: 0.5, size: 0.3,
edgeType: edge.type, edgeType: edge.type,
}); });
} }
@@ -183,14 +183,14 @@ export function NetworkGraph({ data, className }: NetworkGraphProps) {
const sigma = new Sigma(graph, container, { const sigma = new Sigma(graph, container, {
renderEdgeLabels: false, renderEdgeLabels: false,
labelDensity: 0.5, labelDensity: 0.12,
labelRenderedSizeThreshold: 6, labelRenderedSizeThreshold: 14,
zIndex: true, zIndex: true,
defaultEdgeColor: '#ffffff10', defaultEdgeColor: '#ffffff06',
defaultNodeColor: '#6b7280', defaultNodeColor: '#6b7280',
labelColor: { color: '#e8e6f0' }, labelColor: { color: '#e8e6f0' },
labelFont: 'Inter, system-ui, sans-serif', labelFont: 'Inter, system-ui, sans-serif',
labelSize: 12, labelSize: 11,
stagePadding: 40, stagePadding: 40,
nodeReducer: (node, attrs) => { nodeReducer: (node, attrs) => {
const res = { ...attrs }; const res = { ...attrs };

View File

@@ -46,8 +46,7 @@ export function getUserNodeColor(
* Size is proportional to direct_referrals, clamped between min and max. * Size is proportional to direct_referrals, clamped between min and max.
*/ */
export function getUserNodeSize(directReferrals: number): number { export function getUserNodeSize(directReferrals: number): number {
const MIN_SIZE = 12; if (directReferrals === 0) return 5;
const MAX_SIZE = 50; if (directReferrals <= 2) return 10;
if (directReferrals === 0) return MIN_SIZE; return Math.min(40, 10 + Math.sqrt(directReferrals) * 4);
return Math.min(MAX_SIZE, MIN_SIZE + Math.sqrt(directReferrals) * 5);
} }