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="icon" type="image/svg+xml" href="/favicon.svg" />
<title>Mission Control 2.0</title>
<script type="module" crossorigin src="/assets/index-CHmI2uuS.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DHhyicLg.css">
<script type="module" crossorigin src="/assets/index-i5uFkghh.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index--2fz5jZz.css">
</head>
<body>
<div id="root"></div>
+43 -29
View File
@@ -73,19 +73,19 @@ export function GraphView({ data, selectedNodeId, onNodeSelect }: {
// Quadranten-Fokuskoordinaten für sauberes Clustering (Fokuszentren weiter auseinander gezogen)
const getClusterX = (category: string) => {
return {
identity: -220,
knowledge: 220,
rules: -220,
events: 220,
identity: -240,
knowledge: 240,
rules: -240,
events: 240,
}[category] || 0
}
const getClusterY = (category: string) => {
return {
identity: -220,
knowledge: -220,
rules: 220,
events: 220,
identity: -240,
knowledge: -240,
rules: 240,
events: 240,
}[category] || 0
}
@@ -98,23 +98,30 @@ export function GraphView({ data, selectedNodeId, onNodeSelect }: {
fg.d3Force("center", null)
// 2. Clustering-Kräfte (Knoten zu ihren jeweiligen Sektions-Foci ziehen)
fg.d3Force("x", forceX().x((node: any) => getClusterX(node.category)).strength(0.18))
fg.d3Force("y", forceY().y((node: any) => getClusterY(node.category)).strength(0.18))
// Reduzierte Stärke (0.07 statt 0.18), damit sie sich nicht zu einem dichten Brei zusammenballen.
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
// Erhöht auf -380, damit sich die Knoten kräftiger abstoßen.
const charge = fg.d3Force("charge")
if (charge) charge.strength(-240)
if (charge) charge.strength(-380)
// 4. Kollisionskraft (Fester, sicherer Radius, verhindert Überlappung)
fg.d3Force("collide", forceCollide().radius(34).iterations(2))
// 4. Kollisionskraft (Sicherer Radius, verhindert Überlappung)
// 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)
// Längere Verbindungsstriche (85 statt 60), damit das Netz übersichtlicher und "verbindungsreicher" wirkt.
const link = fg.d3Force("link")
if (link) link.distance(60)
if (link) link.distance(85)
// Simulation neu starten (Reheat)
fg.d3ReheatSimulation()
}, [graphData])
}, [graphData, hovered, selectedNodeId])
const activeNode = hovered || selectedNodeId
@@ -189,7 +196,7 @@ export function GraphView({ data, selectedNodeId, onNodeSelect }: {
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 = 6 + Math.min(degree[node.id] || 0, 15) * 0.8
const color = CAT_COLOR[node.category] || "#64748b"
const isHovered = node.id === hovered
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 finalColor = isDimmed ? "#1e293b" : color
// 1. Zeichne den farbigen Knotenpunkt (Kreis)
if (!isDimmed) {
ctx.shadowColor = color
ctx.shadowBlur = isHovered || isSelected ? 12 : 6
ctx.shadowBlur = isHovered || isSelected ? 15 : 6
}
ctx.beginPath()
@@ -210,28 +218,33 @@ export function GraphView({ data, selectedNodeId, onNodeSelect }: {
if (isHovered || isSelected) {
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.lineWidth = 1.5
ctx.stroke()
}
const label = node.content.length > 30 ? node.content.slice(0, 29) + "…" : node.content
const baseFontSize = isHovered || isSelected ? 10 : 9
const fontSize = baseFontSize / Math.max(0.6, globalScale)
// 2. Zeichne das Label nur unter bestimmten Bedingungen (Zoom-abhängig)
// Verhindert Text-Clutter und macht Knotenpunkte permanent sichtbar.
const showLabel = isHovered || isSelected || (globalScale >= 1.25) || (activeNode && connectedNodes.has(node.id))
if (showLabel) {
const label = node.content.length > 35 ? node.content.slice(0, 34) + "…" : node.content
const baseFontSize = isHovered || isSelected ? 10.5 : 9.5
const fontSize = baseFontSize / Math.max(0.5, globalScale)
ctx.font = `${isHovered || isSelected ? "bold" : "500"} ${fontSize}px "Space Grotesk", sans-serif`
const textWidth = ctx.measureText(label).width
const bpad = 4 / Math.max(0.6, globalScale)
const bpad = 4.5 / Math.max(0.5, globalScale)
ctx.fillStyle = isDimmed ? "rgba(10, 15, 25, 0.45)" : "rgba(8, 12, 20, 0.85)"
ctx.fillStyle = isDimmed ? "rgba(10, 15, 25, 0.45)" : "rgba(8, 12, 20, 0.88)"
ctx.beginPath()
const rx = x - textWidth / 2 - bpad
const ry = y + size + (5 / Math.max(0.6, globalScale))
const ry = y + size + (6 / Math.max(0.5, globalScale))
const rw = textWidth + bpad * 2
const rh = fontSize + bpad * 1.5
const rad = 4 / Math.max(0.6, globalScale)
const rh = fontSize + bpad * 1.6
const rad = 4 / Math.max(0.5, globalScale)
ctx.beginPath()
ctx.moveTo(rx + rad, ry)
@@ -246,15 +259,16 @@ export function GraphView({ data, selectedNodeId, onNodeSelect }: {
ctx.closePath()
ctx.fill()
ctx.strokeStyle = isHovered || isSelected ? "rgba(255, 255, 255, 0.15)" : "rgba(255, 255, 255, 0.05)"
ctx.lineWidth = 0.5 / Math.max(0.6, globalScale)
ctx.strokeStyle = isHovered || isSelected ? "rgba(255, 255, 255, 0.2)" : "rgba(255, 255, 255, 0.08)"
ctx.lineWidth = 0.6 / Math.max(0.5, globalScale)
ctx.stroke()
ctx.textAlign = "center"
ctx.textBaseline = "top"
ctx.fillStyle = isDimmed ? "#475569" : (isHovered || isSelected ? "#ffffff" : "#cbd5e1")
ctx.fillText(label, x, y + size + (6 / Math.max(0.6, globalScale)))
ctx.fillText(label, x, y + size + (7 / Math.max(0.5, globalScale)))
}
}}
/>