Politur: Performance-Bottleneck (ctx.measureText) durch clientseitiges Text-Breiten-Caching auf dem Knoten geloest

This commit is contained in:
Hitonabi
2026-07-09 12:41:16 +02:00
parent 082f4a659d
commit 65d5a49411
4 changed files with 29 additions and 23 deletions
+17 -11
View File
@@ -61,7 +61,7 @@ export function GraphView({ data, selectedNodeId, onNodeSelect }: {
}
}, [data])
// Grad (Degree) berechnen fängt D3-Objektmutationen robust ab!
// Grad (Degree) berechnen fängt D3-Objektmutationen robust ab
const degree = useMemo(() => {
const d: Record<string, number> = {}
data.edges.forEach((e) => {
@@ -75,7 +75,7 @@ export function GraphView({ data, selectedNodeId, onNodeSelect }: {
return d
}, [data])
// Quadranten-Fokuskoordinaten für sauberes Clustering (Fokuszentren weiter auseinander gezogen)
// Quadranten-Fokuskoordinaten für sauberes Clustering
const getClusterX = (category: string) => {
return {
identity: -240,
@@ -132,7 +132,7 @@ export function GraphView({ data, selectedNodeId, onNodeSelect }: {
fgRef.current.zoomToFit(400, 40)
}
// Nachbarmenge für extrem schnellen Lookup fängt D3-Objektmutationen robust ab!
// Nachbarmenge für extrem schnellen Lookup fängt D3-Objektmutationen robust ab
const connectedNodes = useMemo(() => {
if (!activeNode) return new Set<string>()
const set = new Set<string>()
@@ -157,15 +157,11 @@ export function GraphView({ data, selectedNodeId, onNodeSelect }: {
backgroundColor="rgba(0,0,0,0)"
cooldownTicks={120}
// Exakte Hit-Map auf unsichtbarem Canvas zeichnen fängt D3-Objektmutationen robust ab!
// Ultraschneller, statischer Klick- & Hover-Puffer auf dem Hit-Canvas (verhindert teure Echtzeitrechnungen)
nodePointerAreaPaint={(node: any, color: string, ctx: CanvasRenderingContext2D) => {
const x = node.x ?? 0
const y = node.y ?? 0
const nId = typeof node === "object" ? node.id : node
const size = 6 + Math.min(degree[nId] || 0, 15) * 0.8
ctx.fillStyle = color
ctx.beginPath()
ctx.arc(x, y, size + 5, 0, 2 * Math.PI, false) // 5px Puffer für einfaches Treffen
ctx.arc(node.x ?? 0, node.y ?? 0, 16, 0, 2 * Math.PI, false)
ctx.fill()
}}
@@ -239,16 +235,26 @@ export function GraphView({ data, selectedNodeId, onNodeSelect }: {
}
// 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
// Textbreite EINMALIG auf dem Objekt cachen (verhindert massive Frame-Einbrüche durch ctx.measureText)
if (node.__baseTextWidth === undefined) {
const prevFont = ctx.font
ctx.font = '500 10px "Space Grotesk", sans-serif'
node.__baseTextWidth = ctx.measureText(label).width
ctx.font = prevFont
}
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
// Breite linear zur geänderten FontSize skalieren KEIN ctx.measureText Aufruf mehr pro Frame!
const textWidth = node.__baseTextWidth * (fontSize / 10)
const bpad = 4.5 / Math.max(0.5, globalScale)
ctx.fillStyle = isDimmed ? "rgba(10, 15, 25, 0.45)" : "rgba(8, 12, 20, 0.88)"