Politur: Robustheits-Glow-Graph-Fixes fuer D3-Simulations-Initialisierung und Performance-Optimierung

This commit is contained in:
Hitonabi
2026-07-09 12:18:18 +02:00
parent 9375b8eea8
commit 520cfd9fe4
4 changed files with 50 additions and 30 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -7,7 +7,7 @@
<link rel="manifest" href="/manifest.webmanifest" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<title>Mission Control 2.0</title>
<script type="module" crossorigin src="/assets/index-CnQbeKKt.js"></script>
<script type="module" crossorigin src="/assets/index-BrDDv3Dl.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-1USExdfg.css">
</head>
<body>
+41 -21
View File
@@ -10,7 +10,7 @@ const CAT_LABEL: Record<string, string> = {
identity: "Identität", knowledge: "Wissen", rules: "Regeln", events: "Ereignisse",
}
const AUTO = new Set(["auto", "agent", "hermes"])
const DIM_EDGE = "rgba(15, 23, 42, 0.02)"
const DIM_EDGE = "rgba(15, 23, 42, 0.01)"
const EDGE = "rgba(255, 255, 255, 0.05)"
const EDGE_HI = "rgba(99, 102, 241, 0.85)"
@@ -27,10 +27,12 @@ export function GraphView({ data, onDelete }: { data: MemoryGraph; onDelete: (id
if (!containerRef.current) return
const resizeObserver = new ResizeObserver((entries) => {
for (let entry of entries) {
setDimensions({
width: Math.floor(entry.contentRect.width),
height: Math.floor(entry.contentRect.height),
})
if (entry.contentRect.width > 0 && entry.contentRect.height > 0) {
setDimensions({
width: Math.floor(entry.contentRect.width),
height: Math.floor(entry.contentRect.height),
})
}
}
})
resizeObserver.observe(containerRef.current)
@@ -60,10 +62,15 @@ export function GraphView({ data, onDelete }: { data: MemoryGraph; onDelete: (id
// D3 Force Engine tunen (Luftige Struktur, kein Überlappen)
useEffect(() => {
if (!fgRef.current) return
const fg = fgRef.current
fg.d3Force("charge").strength(-160)
fg.d3Force("link").distance(65)
if (!fg) return
// Sicherstellen, dass D3-Kräfte existieren, bevor wir aufgerufen werden
const charge = fg.d3Force("charge")
if (charge) charge.strength(-140)
const link = fg.d3Force("link")
if (link) link.distance(60)
}, [graphData])
const activeNode = hovered || selected
@@ -75,6 +82,8 @@ export function GraphView({ data, onDelete }: { data: MemoryGraph; onDelete: (id
}
const sel = selected ? data.nodes.find((n) => n.id === selected) ?? null : null
// Nachbarn über das statische, sichere Props-Array ermitteln
const neighbors = useMemo(() => {
if (!selected) return []
const ids = new Set<string>()
@@ -85,6 +94,17 @@ export function GraphView({ data, onDelete }: { data: MemoryGraph; onDelete: (id
return data.nodes.filter((n) => ids.has(n.id))
}, [selected, data])
// Nachbarmenge für extrem schnellen Lookup im Render-Loop
const connectedNodes = useMemo(() => {
if (!activeNode) return new Set<string>()
const set = new Set<string>()
data.edges.forEach((e) => {
if (e.source === activeNode) set.add(e.target)
if (e.target === activeNode) set.add(e.source)
})
return set
}, [activeNode, data])
if (!data.nodes.length) {
return (
<div className="text-xs text-muted-foreground border border-dashed border-border/60 rounded-2xl p-12 text-center">
@@ -106,13 +126,16 @@ export function GraphView({ data, onDelete }: { data: MemoryGraph; onDelete: (id
graphData={graphData}
backgroundColor="rgba(0,0,0,0)"
nodeRelSize={4}
cooldownTicks={100}
// Hover- und Klick-Logik
onNodeClick={(node: any) => setSelected(node.id)}
onBackgroundClick={() => setSelected(null)}
onNodeHover={(node: any) => {
setHovered(node ? node.id : null)
containerRef.current!.style.cursor = node ? "pointer" : "default"
if (containerRef.current) {
containerRef.current.style.cursor = node ? "pointer" : "default"
}
}}
// Kanten-Animationen & Partikelfluss
@@ -139,18 +162,15 @@ export function GraphView({ data, onDelete }: { data: MemoryGraph; onDelete: (id
// Custom Canvas Rendering für glühende Knoten und 100% lesbare Text-Pillen
nodeCanvasObject={(node: any, ctx: CanvasRenderingContext2D, globalScale: number) => {
const x = node.x ?? 0
const y = node.y ?? 0
const size = 5.5 + Math.min(degree[node.id] || 0, 12) * 0.9
const color = CAT_COLOR[node.category] || "#64748b"
const isHovered = node.id === hovered
const isSelected = node.id === selected
// Abgedunkelter Zustand, wenn ein anderer Knoten aktiv ist
const isDimmed = activeNode && node.id !== activeNode && !fgRef.current?.getGraphData()?.links.some((l: any) => {
const sId = typeof l.source === "object" ? l.source.id : l.source
const tId = typeof l.target === "object" ? l.target.id : l.target
return (sId === activeNode && tId === node.id) || (tId === activeNode && sId === node.id)
})
// Abgedunkelter Zustand, wenn ein anderer Knoten aktiv ist und dieser kein Nachbar ist
const isDimmed = activeNode && node.id !== activeNode && !connectedNodes.has(node.id)
const finalColor = isDimmed ? "#1e293b" : color
// 1. Zeichne weichen Glow-Schatten
@@ -161,7 +181,7 @@ export function GraphView({ data, onDelete }: { data: MemoryGraph; onDelete: (id
// 2. Zeichne den Knoten-Kreis
ctx.beginPath()
ctx.arc(node.x, node.y, size, 0, 2 * Math.PI, false)
ctx.arc(x, y, size, 0, 2 * Math.PI, false)
ctx.fillStyle = finalColor
ctx.fill()
ctx.shadowBlur = 0 // Schatten zurücksetzen
@@ -169,7 +189,7 @@ export function GraphView({ data, onDelete }: { data: MemoryGraph; onDelete: (id
// 3. Zeichne weißen Außenring bei Interaktivität
if (isHovered || isSelected) {
ctx.beginPath()
ctx.arc(node.x, node.y, size + 2, 0, 2 * Math.PI, false)
ctx.arc(x, y, size + 2, 0, 2 * Math.PI, false)
ctx.strokeStyle = isSelected ? "#ffffff" : "rgba(255, 255, 255, 0.7)"
ctx.lineWidth = 1.5
ctx.stroke()
@@ -189,8 +209,8 @@ export function GraphView({ data, onDelete }: { data: MemoryGraph; onDelete: (id
ctx.beginPath()
// Zeichne abgerundete Pille unter dem Knoten
const rx = node.x - textWidth / 2 - bpad
const ry = node.y + size + (5 / Math.max(0.6, globalScale))
const rx = x - textWidth / 2 - bpad
const ry = y + size + (5 / Math.max(0.6, globalScale))
const rw = textWidth + bpad * 2
const rh = fontSize + bpad * 1.5
const rad = 4 / Math.max(0.6, globalScale)
@@ -220,7 +240,7 @@ export function GraphView({ data, onDelete }: { data: MemoryGraph; onDelete: (id
? "#475569"
: (isHovered || isSelected ? "#ffffff" : "#cbd5e1")
ctx.fillText(label, node.x, node.y + size + (6 / Math.max(0.6, globalScale)))
ctx.fillText(label, x, y + size + (6 / Math.max(0.6, globalScale)))
}}
/>