Politur: Zoom-abhaengiges Rendering fuer Gedaechtnis-Labels und staerkere Physik-Abstossung

This commit is contained in:
Hitonabi
2026-07-09 12:31:14 +02:00
parent dec4922171
commit 90d8a7d56d
6 changed files with 80 additions and 66 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -7,8 +7,8 @@
<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-CHmI2uuS.js"></script> <script type="module" crossorigin src="/assets/index-i5uFkghh.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DHhyicLg.css"> <link rel="stylesheet" crossorigin href="/assets/index--2fz5jZz.css">
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
+64 -50
View File
@@ -73,19 +73,19 @@ export function GraphView({ data, selectedNodeId, onNodeSelect }: {
// Quadranten-Fokuskoordinaten für sauberes Clustering (Fokuszentren weiter auseinander gezogen) // Quadranten-Fokuskoordinaten für sauberes Clustering (Fokuszentren weiter auseinander gezogen)
const getClusterX = (category: string) => { const getClusterX = (category: string) => {
return { return {
identity: -220, identity: -240,
knowledge: 220, knowledge: 240,
rules: -220, rules: -240,
events: 220, events: 240,
}[category] || 0 }[category] || 0
} }
const getClusterY = (category: string) => { const getClusterY = (category: string) => {
return { return {
identity: -220, identity: -240,
knowledge: -220, knowledge: -240,
rules: 220, rules: 240,
events: 220, events: 240,
}[category] || 0 }[category] || 0
} }
@@ -98,23 +98,30 @@ export function GraphView({ data, selectedNodeId, onNodeSelect }: {
fg.d3Force("center", null) fg.d3Force("center", null)
// 2. Clustering-Kräfte (Knoten zu ihren jeweiligen Sektions-Foci ziehen) // 2. Clustering-Kräfte (Knoten zu ihren jeweiligen Sektions-Foci ziehen)
fg.d3Force("x", forceX().x((node: any) => getClusterX(node.category)).strength(0.18)) // Reduzierte Stärke (0.07 statt 0.18), damit sie sich nicht zu einem dichten Brei zusammenballen.
fg.d3Force("y", forceY().y((node: any) => getClusterY(node.category)).strength(0.18)) fg.d3Force("x", forceX().x((node: any) => getClusterX(node.category)).strength(0.07))
fg.d3Force("y", forceY().y((node: any) => getClusterY(node.category)).strength(0.07))
// 3. Starke Abstoßung (Charge) für ein weit gefächertes, luftiges Netz // 3. Starke Abstoßung (Charge) für ein weit gefächertes, luftiges Netz
// Erhöht auf -380, damit sich die Knoten kräftiger abstoßen.
const charge = fg.d3Force("charge") const charge = fg.d3Force("charge")
if (charge) charge.strength(-240) if (charge) charge.strength(-380)
// 4. Kollisionskraft (Fester, sicherer Radius, verhindert Überlappung) // 4. Kollisionskraft (Sicherer Radius, verhindert Überlappung)
fg.d3Force("collide", forceCollide().radius(34).iterations(2)) // Wenn der Knoten hovered oder selected ist, geben wir ihm mehr Platz
fg.d3Force("collide", forceCollide().radius((node: any) => {
const isLarge = node.id === hovered || node.id === selectedNodeId
return isLarge ? 50 : 25
}).iterations(2))
// 5. Federkraft (Links) // 5. Federkraft (Links)
// Längere Verbindungsstriche (85 statt 60), damit das Netz übersichtlicher und "verbindungsreicher" wirkt.
const link = fg.d3Force("link") const link = fg.d3Force("link")
if (link) link.distance(60) if (link) link.distance(85)
// Simulation neu starten (Reheat) // Simulation neu starten (Reheat)
fg.d3ReheatSimulation() fg.d3ReheatSimulation()
}, [graphData]) }, [graphData, hovered, selectedNodeId])
const activeNode = hovered || selectedNodeId const activeNode = hovered || selectedNodeId
@@ -189,7 +196,7 @@ export function GraphView({ data, selectedNodeId, onNodeSelect }: {
nodeCanvasObject={(node: any, ctx: CanvasRenderingContext2D, globalScale: number) => { nodeCanvasObject={(node: any, ctx: CanvasRenderingContext2D, globalScale: number) => {
const x = node.x ?? 0 const x = node.x ?? 0
const y = node.y ?? 0 const y = node.y ?? 0
const size = 5.5 + Math.min(degree[node.id] || 0, 12) * 0.9 const size = 6 + Math.min(degree[node.id] || 0, 15) * 0.8
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 === selectedNodeId const isSelected = node.id === selectedNodeId
@@ -197,9 +204,10 @@ export function GraphView({ data, selectedNodeId, onNodeSelect }: {
const isDimmed = activeNode && node.id !== activeNode && !connectedNodes.has(node.id) const isDimmed = activeNode && node.id !== activeNode && !connectedNodes.has(node.id)
const finalColor = isDimmed ? "#1e293b" : color const finalColor = isDimmed ? "#1e293b" : color
// 1. Zeichne den farbigen Knotenpunkt (Kreis)
if (!isDimmed) { if (!isDimmed) {
ctx.shadowColor = color ctx.shadowColor = color
ctx.shadowBlur = isHovered || isSelected ? 12 : 6 ctx.shadowBlur = isHovered || isSelected ? 15 : 6
} }
ctx.beginPath() ctx.beginPath()
@@ -210,51 +218,57 @@ export function GraphView({ data, selectedNodeId, onNodeSelect }: {
if (isHovered || isSelected) { if (isHovered || isSelected) {
ctx.beginPath() ctx.beginPath()
ctx.arc(x, y, size + 2, 0, 2 * Math.PI, false) ctx.arc(x, y, size + 2.5, 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()
} }
const label = node.content.length > 30 ? node.content.slice(0, 29) + "…" : node.content // 2. Zeichne das Label nur unter bestimmten Bedingungen (Zoom-abhängig)
const baseFontSize = isHovered || isSelected ? 10 : 9 // Verhindert Text-Clutter und macht Knotenpunkte permanent sichtbar.
const fontSize = baseFontSize / Math.max(0.6, globalScale) const showLabel = isHovered || isSelected || (globalScale >= 1.25) || (activeNode && connectedNodes.has(node.id))
ctx.font = `${isHovered || isSelected ? "bold" : "500"} ${fontSize}px "Space Grotesk", sans-serif` if (showLabel) {
const textWidth = ctx.measureText(label).width const label = node.content.length > 35 ? node.content.slice(0, 34) + "…" : node.content
const bpad = 4 / Math.max(0.6, globalScale) const baseFontSize = isHovered || isSelected ? 10.5 : 9.5
const fontSize = baseFontSize / Math.max(0.5, globalScale)
ctx.fillStyle = isDimmed ? "rgba(10, 15, 25, 0.45)" : "rgba(8, 12, 20, 0.85)" ctx.font = `${isHovered || isSelected ? "bold" : "500"} ${fontSize}px "Space Grotesk", sans-serif`
ctx.beginPath() const textWidth = ctx.measureText(label).width
const bpad = 4.5 / Math.max(0.5, globalScale)
const rx = x - textWidth / 2 - bpad ctx.fillStyle = isDimmed ? "rgba(10, 15, 25, 0.45)" : "rgba(8, 12, 20, 0.88)"
const ry = y + size + (5 / Math.max(0.6, globalScale)) ctx.beginPath()
const rw = textWidth + bpad * 2
const rh = fontSize + bpad * 1.5
const rad = 4 / Math.max(0.6, globalScale)
ctx.beginPath() const rx = x - textWidth / 2 - bpad
ctx.moveTo(rx + rad, ry) const ry = y + size + (6 / Math.max(0.5, globalScale))
ctx.lineTo(rx + rw - rad, ry) const rw = textWidth + bpad * 2
ctx.quadraticCurveTo(rx + rw, ry, rx + rw, ry + rad) const rh = fontSize + bpad * 1.6
ctx.lineTo(rx + rw, ry + rh - rad) const rad = 4 / Math.max(0.5, globalScale)
ctx.quadraticCurveTo(rx + rw, ry + rh, rx + rw - rad, ry + rh)
ctx.lineTo(rx + rad, ry + rh)
ctx.quadraticCurveTo(rx, ry + rh, rx, ry + rh - rad)
ctx.lineTo(rx, ry + rad)
ctx.quadraticCurveTo(rx, ry, rx + rad, ry)
ctx.closePath()
ctx.fill()
ctx.strokeStyle = isHovered || isSelected ? "rgba(255, 255, 255, 0.15)" : "rgba(255, 255, 255, 0.05)" ctx.beginPath()
ctx.lineWidth = 0.5 / Math.max(0.6, globalScale) ctx.moveTo(rx + rad, ry)
ctx.stroke() ctx.lineTo(rx + rw - rad, ry)
ctx.quadraticCurveTo(rx + rw, ry, rx + rw, ry + rad)
ctx.lineTo(rx + rw, ry + rh - rad)
ctx.quadraticCurveTo(rx + rw, ry + rh, rx + rw - rad, ry + rh)
ctx.lineTo(rx + rad, ry + rh)
ctx.quadraticCurveTo(rx, ry + rh, rx, ry + rh - rad)
ctx.lineTo(rx, ry + rad)
ctx.quadraticCurveTo(rx, ry, rx + rad, ry)
ctx.closePath()
ctx.fill()
ctx.textAlign = "center" ctx.strokeStyle = isHovered || isSelected ? "rgba(255, 255, 255, 0.2)" : "rgba(255, 255, 255, 0.08)"
ctx.textBaseline = "top" ctx.lineWidth = 0.6 / Math.max(0.5, globalScale)
ctx.fillStyle = isDimmed ? "#475569" : (isHovered || isSelected ? "#ffffff" : "#cbd5e1") ctx.stroke()
ctx.fillText(label, x, y + size + (6 / Math.max(0.6, globalScale))) ctx.textAlign = "center"
ctx.textBaseline = "top"
ctx.fillStyle = isDimmed ? "#475569" : (isHovered || isSelected ? "#ffffff" : "#cbd5e1")
ctx.fillText(label, x, y + size + (7 / Math.max(0.5, globalScale)))
}
}} }}
/> />