Politur: Gedaechnis-Graph um Quadranten-Clustering und forceCollide-Kollisionstuning erweitert (vollstaendige Entzerrung und farbige, gut sichtbare Kanten)
This commit is contained in:
+44
File diff suppressed because one or more lines are too long
-44
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="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<title>Mission Control 2.0</title>
|
||||
<script type="module" crossorigin src="/assets/index-BrDDv3Dl.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-BcsTzr2v.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-1USExdfg.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
Generated
+23
@@ -14,6 +14,7 @@
|
||||
"@tanstack/react-query": "^5.101.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.1.1",
|
||||
"d3-force": "^3.0.0",
|
||||
"graphology": "^0.26.0",
|
||||
"graphology-layout-forceatlas2": "^0.10.1",
|
||||
"lucide-react": "^0.460.0",
|
||||
@@ -27,6 +28,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/vite": "^4.0.0",
|
||||
"@types/d3-force": "^3.0.10",
|
||||
"@types/node": "^22.10.1",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
@@ -2345,6 +2347,13 @@
|
||||
"integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/d3-force": {
|
||||
"version": "3.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz",
|
||||
"integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/d3-interpolate": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
|
||||
@@ -2838,6 +2847,20 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-force": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz",
|
||||
"integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"d3-dispatch": "1 - 3",
|
||||
"d3-quadtree": "1 - 3",
|
||||
"d3-timer": "1 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-force-3d": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/d3-force-3d/-/d3-force-3d-3.0.6.tgz",
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
"@tanstack/react-query": "^5.101.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.1.1",
|
||||
"d3-force": "^3.0.0",
|
||||
"graphology": "^0.26.0",
|
||||
"graphology-layout-forceatlas2": "^0.10.1",
|
||||
"lucide-react": "^0.460.0",
|
||||
@@ -28,6 +29,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/vite": "^4.0.0",
|
||||
"@types/d3-force": "^3.0.10",
|
||||
"@types/node": "^22.10.1",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useEffect, useMemo, useRef, useState } from "react"
|
||||
import ForceGraph2D from "react-force-graph-2d"
|
||||
import { forceCollide, forceX, forceY } from "d3-force"
|
||||
import { Trash2, Sparkles, Share2, RefreshCw } from "lucide-react"
|
||||
import { type MemoryGraph } from "@/lib/api"
|
||||
|
||||
@@ -10,9 +11,16 @@ 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.01)"
|
||||
const EDGE = "rgba(255, 255, 255, 0.05)"
|
||||
const EDGE_HI = "rgba(99, 102, 241, 0.85)"
|
||||
const DIM_EDGE = "rgba(255, 255, 255, 0.02)"
|
||||
|
||||
// Hilfsfunktion zur Konvertierung von Hex in RGBA für farbkodierte Verbindungen
|
||||
function hexToRgba(hex: string, alpha: number): string {
|
||||
const r = parseInt(hex.slice(1, 3), 16)
|
||||
const g = parseInt(hex.slice(3, 5), 16)
|
||||
const b = parseInt(hex.slice(5, 7), 16)
|
||||
return `rgba(${r}, ${g}, ${b}, ${alpha})`
|
||||
}
|
||||
|
||||
export function GraphView({ data, onDelete }: { data: MemoryGraph; onDelete: (id: string) => void }) {
|
||||
const [selected, setSelected] = useState<string | null>(null)
|
||||
@@ -60,17 +68,51 @@ export function GraphView({ data, onDelete }: { data: MemoryGraph; onDelete: (id
|
||||
return d
|
||||
}, [data])
|
||||
|
||||
// D3 Force Engine tunen (Luftige Struktur, kein Überlappen)
|
||||
// Quadranten-Fokuskoordinaten für sauberes Clustering
|
||||
const getClusterX = (category: string) => {
|
||||
return {
|
||||
identity: -160,
|
||||
knowledge: 160,
|
||||
rules: -160,
|
||||
events: 160,
|
||||
}[category] || 0
|
||||
}
|
||||
|
||||
const getClusterY = (category: string) => {
|
||||
return {
|
||||
identity: -160,
|
||||
knowledge: -160,
|
||||
rules: 160,
|
||||
events: 160,
|
||||
}[category] || 0
|
||||
}
|
||||
|
||||
// D3 Force Engine tunen (Luftige Struktur, kein Überlappen, Clustering)
|
||||
useEffect(() => {
|
||||
const fg = fgRef.current
|
||||
if (!fg) return
|
||||
|
||||
// Sicherstellen, dass D3-Kräfte existieren, bevor wir aufgerufen werden
|
||||
const charge = fg.d3Force("charge")
|
||||
if (charge) charge.strength(-140)
|
||||
// 1. Clustering-Kräfte (Knoten zu ihren jeweiligen Sektions-Foci ziehen)
|
||||
fg.d3Force("x", forceX().x((node: any) => getClusterX(node.category)).strength(0.14))
|
||||
fg.d3Force("y", forceY().y((node: any) => getClusterY(node.category)).strength(0.14))
|
||||
|
||||
// 2. Kollisionskraft (Verhindert, dass Knoten und Textlabels ineinandergeschoben werden)
|
||||
fg.d3Force("collide", forceCollide().radius((node: any) => {
|
||||
const nodeDegree = degree[node.id] || 0
|
||||
// Radius = Knotengröße + großzügiger Textpuffer
|
||||
return 28 + Math.min(nodeDegree, 12) * 1.5
|
||||
}).iterations(2))
|
||||
|
||||
// 3. Abstoßung (Charge) moderat, da collide und foci die Hauptarbeit leisten
|
||||
const charge = fg.d3Force("charge")
|
||||
if (charge) charge.strength(-60)
|
||||
|
||||
// 4. Federn (Links)
|
||||
const link = fg.d3Force("link")
|
||||
if (link) link.distance(60)
|
||||
if (link) link.distance(50)
|
||||
|
||||
// Simulation neu anschubsen bei neuen Daten
|
||||
fg.alpha(0.8).restart()
|
||||
}, [graphData])
|
||||
|
||||
const activeNode = hovered || selected
|
||||
@@ -126,7 +168,7 @@ export function GraphView({ data, onDelete }: { data: MemoryGraph; onDelete: (id
|
||||
graphData={graphData}
|
||||
backgroundColor="rgba(0,0,0,0)"
|
||||
nodeRelSize={4}
|
||||
cooldownTicks={100}
|
||||
cooldownTicks={120}
|
||||
|
||||
// Hover- und Klick-Logik
|
||||
onNodeClick={(node: any) => setSelected(node.id)}
|
||||
@@ -142,13 +184,22 @@ export function GraphView({ data, onDelete }: { data: MemoryGraph; onDelete: (id
|
||||
linkColor={(link: any) => {
|
||||
const sId = typeof link.source === "object" ? link.source.id : link.source
|
||||
const tId = typeof link.target === "object" ? link.target.id : link.target
|
||||
|
||||
// Highlight-Kanten leuchten in Indigo auf
|
||||
if (activeNode === sId || activeNode === tId) return EDGE_HI
|
||||
return activeNode ? DIM_EDGE : EDGE
|
||||
|
||||
// Wenn ein anderer Knoten aktiv ist, blenden wir diese Kante fast komplett aus
|
||||
if (activeNode) return DIM_EDGE
|
||||
|
||||
// Standard-Zustand: Kante leuchtet dezent in der Farbe der Kategorie des Quell-Knotens
|
||||
const sourceNode = graphData.nodes.find(n => n.id === sId)
|
||||
const catColor = sourceNode ? CAT_COLOR[sourceNode.category] : "#64748b"
|
||||
return hexToRgba(catColor, 0.16)
|
||||
}}
|
||||
linkWidth={(link: any) => {
|
||||
const sId = typeof link.source === "object" ? link.source.id : link.source
|
||||
const tId = typeof link.target === "object" ? link.target.id : link.target
|
||||
return (activeNode === sId || activeNode === tId) ? 2.2 : 0.8
|
||||
return (activeNode === sId || activeNode === tId) ? 2.5 : 1.2
|
||||
}}
|
||||
|
||||
// Daten-Partikel fließen auf den aktiven Verbindungen
|
||||
|
||||
Reference in New Issue
Block a user