import React, { useEffect, useRef, useMemo } from 'react'; import { Network } from 'vis-network/standalone'; import { LAYOUT_MODES } from '../../utils/settings'; /** Known node positions for explicit-coordinate mode (vis-network canvas coords). */ const EXPLICIT_POSITIONS = { temperature: { x: 0, y: -140 }, D1: { x: -120, y: 20 }, D2: { x: 120, y: 20 }, 'actuator-1': { x: 0, y: 140 }, D3: { x: 220, y: 20 }, }; function fallbackPosition(nodeId, index) { const angle = (index * 2 * Math.PI) / 8; return { x: Math.cos(angle) * 180, y: Math.sin(angle) * 120 }; } function applyLayout(data, layoutMode) { const base = data || { nodes: [], edges: [] }; const nodes = (base.nodes || []).map((node, index) => { if (layoutMode !== LAYOUT_MODES.EXPLICIT) return { ...node }; const pos = EXPLICIT_POSITIONS[node.id] ?? fallbackPosition(node.id, index); return { ...node, x: pos.x, y: pos.y, fixed: true }; }); return { nodes, edges: base.edges || [] }; } function buildOptions(layoutMode) { const options = { autoResize: true, nodes: { borderWidth: 2, font: { color: '#ffffff', size: 14 }, }, edges: { arrows: 'to', color: { color: '#9aa3b2', highlight: '#6366F1' }, font: { size: 10, color: '#9aa3b2', strokeWidth: 0, align: 'middle' }, smooth: { type: 'cubicBezier', roundness: 0.4 }, }, groups: { sensor: { shape: 'box', color: { background: '#3b82f6', border: '#1d4ed8' } }, decidron: { shape: 'hexagon', color: { background: '#8b5cf6', border: '#6d28d9' } }, actuator: { shape: 'triangle', color: { background: '#10b981', border: '#047857' } }, }, interaction: { hover: true, tooltipDelay: 120 }, }; if (layoutMode === LAYOUT_MODES.EXPLICIT) { options.physics = { enabled: false }; } else { options.physics = { stabilization: { iterations: 150 }, barnesHut: { springLength: 150, gravitationalConstant: -6000 }, }; if (layoutMode === LAYOUT_MODES.FIXED_SEED) { options.layout = { randomSeed: 42 }; } // RANDOM: no randomSeed — vis-network picks a new seed each build (default). } return options; } export default function NetworkDiagram({ data, layoutMode = LAYOUT_MODES.RANDOM, height = 380 }) { const containerRef = useRef(null); const networkRef = useRef(null); const graphData = useMemo( () => applyLayout(data, layoutMode), [data, layoutMode], ); useEffect(() => { if (!containerRef.current) return undefined; networkRef.current = new Network( containerRef.current, graphData, buildOptions(layoutMode), ); return () => { if (networkRef.current) { networkRef.current.destroy(); networkRef.current = null; } }; }, [graphData, layoutMode]); return (
); }