Politur: Robustheits-Glow-Graph-Fixes fuer D3-Simulations-Initialisierung und Performance-Optimierung
This commit is contained in:
+7
-7
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -7,7 +7,7 @@
|
|||||||
<link rel="manifest" href="/manifest.webmanifest" />
|
<link rel="manifest" href="/manifest.webmanifest" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<title>Mission Control 2.0</title>
|
<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">
|
<link rel="stylesheet" crossorigin href="/assets/index-1USExdfg.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ const CAT_LABEL: Record<string, string> = {
|
|||||||
identity: "Identität", knowledge: "Wissen", rules: "Regeln", events: "Ereignisse",
|
identity: "Identität", knowledge: "Wissen", rules: "Regeln", events: "Ereignisse",
|
||||||
}
|
}
|
||||||
const AUTO = new Set(["auto", "agent", "hermes"])
|
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 = "rgba(255, 255, 255, 0.05)"
|
||||||
const EDGE_HI = "rgba(99, 102, 241, 0.85)"
|
const EDGE_HI = "rgba(99, 102, 241, 0.85)"
|
||||||
|
|
||||||
@@ -27,11 +27,13 @@ export function GraphView({ data, onDelete }: { data: MemoryGraph; onDelete: (id
|
|||||||
if (!containerRef.current) return
|
if (!containerRef.current) return
|
||||||
const resizeObserver = new ResizeObserver((entries) => {
|
const resizeObserver = new ResizeObserver((entries) => {
|
||||||
for (let entry of entries) {
|
for (let entry of entries) {
|
||||||
|
if (entry.contentRect.width > 0 && entry.contentRect.height > 0) {
|
||||||
setDimensions({
|
setDimensions({
|
||||||
width: Math.floor(entry.contentRect.width),
|
width: Math.floor(entry.contentRect.width),
|
||||||
height: Math.floor(entry.contentRect.height),
|
height: Math.floor(entry.contentRect.height),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
resizeObserver.observe(containerRef.current)
|
resizeObserver.observe(containerRef.current)
|
||||||
return () => resizeObserver.disconnect()
|
return () => resizeObserver.disconnect()
|
||||||
@@ -60,10 +62,15 @@ export function GraphView({ data, onDelete }: { data: MemoryGraph; onDelete: (id
|
|||||||
|
|
||||||
// D3 Force Engine tunen (Luftige Struktur, kein Überlappen)
|
// D3 Force Engine tunen (Luftige Struktur, kein Überlappen)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!fgRef.current) return
|
|
||||||
const fg = fgRef.current
|
const fg = fgRef.current
|
||||||
fg.d3Force("charge").strength(-160)
|
if (!fg) return
|
||||||
fg.d3Force("link").distance(65)
|
|
||||||
|
// 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])
|
}, [graphData])
|
||||||
|
|
||||||
const activeNode = hovered || selected
|
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
|
const sel = selected ? data.nodes.find((n) => n.id === selected) ?? null : null
|
||||||
|
|
||||||
|
// Nachbarn über das statische, sichere Props-Array ermitteln
|
||||||
const neighbors = useMemo(() => {
|
const neighbors = useMemo(() => {
|
||||||
if (!selected) return []
|
if (!selected) return []
|
||||||
const ids = new Set<string>()
|
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))
|
return data.nodes.filter((n) => ids.has(n.id))
|
||||||
}, [selected, data])
|
}, [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) {
|
if (!data.nodes.length) {
|
||||||
return (
|
return (
|
||||||
<div className="text-xs text-muted-foreground border border-dashed border-border/60 rounded-2xl p-12 text-center">
|
<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}
|
graphData={graphData}
|
||||||
backgroundColor="rgba(0,0,0,0)"
|
backgroundColor="rgba(0,0,0,0)"
|
||||||
nodeRelSize={4}
|
nodeRelSize={4}
|
||||||
|
cooldownTicks={100}
|
||||||
|
|
||||||
// Hover- und Klick-Logik
|
// Hover- und Klick-Logik
|
||||||
onNodeClick={(node: any) => setSelected(node.id)}
|
onNodeClick={(node: any) => setSelected(node.id)}
|
||||||
onBackgroundClick={() => setSelected(null)}
|
onBackgroundClick={() => setSelected(null)}
|
||||||
onNodeHover={(node: any) => {
|
onNodeHover={(node: any) => {
|
||||||
setHovered(node ? node.id : null)
|
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
|
// 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
|
// Custom Canvas Rendering für glühende Knoten und 100% lesbare Text-Pillen
|
||||||
nodeCanvasObject={(node: any, ctx: CanvasRenderingContext2D, globalScale: number) => {
|
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 size = 5.5 + Math.min(degree[node.id] || 0, 12) * 0.9
|
||||||
const color = CAT_COLOR[node.category] || "#64748b"
|
const color = CAT_COLOR[node.category] || "#64748b"
|
||||||
const isHovered = node.id === hovered
|
const isHovered = node.id === hovered
|
||||||
const isSelected = node.id === selected
|
const isSelected = node.id === selected
|
||||||
|
|
||||||
// Abgedunkelter Zustand, wenn ein anderer Knoten aktiv ist
|
// Abgedunkelter Zustand, wenn ein anderer Knoten aktiv ist und dieser kein Nachbar ist
|
||||||
const isDimmed = activeNode && node.id !== activeNode && !fgRef.current?.getGraphData()?.links.some((l: any) => {
|
const isDimmed = activeNode && node.id !== activeNode && !connectedNodes.has(node.id)
|
||||||
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)
|
|
||||||
})
|
|
||||||
|
|
||||||
const finalColor = isDimmed ? "#1e293b" : color
|
const finalColor = isDimmed ? "#1e293b" : color
|
||||||
|
|
||||||
// 1. Zeichne weichen Glow-Schatten
|
// 1. Zeichne weichen Glow-Schatten
|
||||||
@@ -161,7 +181,7 @@ export function GraphView({ data, onDelete }: { data: MemoryGraph; onDelete: (id
|
|||||||
|
|
||||||
// 2. Zeichne den Knoten-Kreis
|
// 2. Zeichne den Knoten-Kreis
|
||||||
ctx.beginPath()
|
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.fillStyle = finalColor
|
||||||
ctx.fill()
|
ctx.fill()
|
||||||
ctx.shadowBlur = 0 // Schatten zurücksetzen
|
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
|
// 3. Zeichne weißen Außenring bei Interaktivität
|
||||||
if (isHovered || isSelected) {
|
if (isHovered || isSelected) {
|
||||||
ctx.beginPath()
|
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.strokeStyle = isSelected ? "#ffffff" : "rgba(255, 255, 255, 0.7)"
|
||||||
ctx.lineWidth = 1.5
|
ctx.lineWidth = 1.5
|
||||||
ctx.stroke()
|
ctx.stroke()
|
||||||
@@ -189,8 +209,8 @@ export function GraphView({ data, onDelete }: { data: MemoryGraph; onDelete: (id
|
|||||||
ctx.beginPath()
|
ctx.beginPath()
|
||||||
|
|
||||||
// Zeichne abgerundete Pille unter dem Knoten
|
// Zeichne abgerundete Pille unter dem Knoten
|
||||||
const rx = node.x - textWidth / 2 - bpad
|
const rx = x - textWidth / 2 - bpad
|
||||||
const ry = node.y + size + (5 / Math.max(0.6, globalScale))
|
const ry = y + size + (5 / Math.max(0.6, globalScale))
|
||||||
const rw = textWidth + bpad * 2
|
const rw = textWidth + bpad * 2
|
||||||
const rh = fontSize + bpad * 1.5
|
const rh = fontSize + bpad * 1.5
|
||||||
const rad = 4 / Math.max(0.6, globalScale)
|
const rad = 4 / Math.max(0.6, globalScale)
|
||||||
@@ -220,7 +240,7 @@ export function GraphView({ data, onDelete }: { data: MemoryGraph; onDelete: (id
|
|||||||
? "#475569"
|
? "#475569"
|
||||||
: (isHovered || isSelected ? "#ffffff" : "#cbd5e1")
|
: (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