Politur: Robustheits-Glow-Graph-Fixes fuer D3-Simulations-Initialisierung und Performance-Optimierung
This commit is contained in:
@@ -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)))
|
||||
}}
|
||||
/>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user