Politur: Performance-Bottleneck (ctx.measureText) durch clientseitiges Text-Breiten-Caching auf dem Knoten geloest
This commit is contained in:
+10
-10
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-Bz6q5kVA.js"></script>
|
<script type="module" crossorigin src="/assets/index-D663kYCx.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index--2fz5jZz.css">
|
<link rel="stylesheet" crossorigin href="/assets/index--2fz5jZz.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export function GraphView({ data, selectedNodeId, onNodeSelect }: {
|
|||||||
}
|
}
|
||||||
}, [data])
|
}, [data])
|
||||||
|
|
||||||
// Grad (Degree) berechnen – fängt D3-Objektmutationen robust ab!
|
// Grad (Degree) berechnen – fängt D3-Objektmutationen robust ab
|
||||||
const degree = useMemo(() => {
|
const degree = useMemo(() => {
|
||||||
const d: Record<string, number> = {}
|
const d: Record<string, number> = {}
|
||||||
data.edges.forEach((e) => {
|
data.edges.forEach((e) => {
|
||||||
@@ -75,7 +75,7 @@ export function GraphView({ data, selectedNodeId, onNodeSelect }: {
|
|||||||
return d
|
return d
|
||||||
}, [data])
|
}, [data])
|
||||||
|
|
||||||
// Quadranten-Fokuskoordinaten für sauberes Clustering (Fokuszentren weiter auseinander gezogen)
|
// Quadranten-Fokuskoordinaten für sauberes Clustering
|
||||||
const getClusterX = (category: string) => {
|
const getClusterX = (category: string) => {
|
||||||
return {
|
return {
|
||||||
identity: -240,
|
identity: -240,
|
||||||
@@ -132,7 +132,7 @@ export function GraphView({ data, selectedNodeId, onNodeSelect }: {
|
|||||||
fgRef.current.zoomToFit(400, 40)
|
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(() => {
|
const connectedNodes = useMemo(() => {
|
||||||
if (!activeNode) return new Set<string>()
|
if (!activeNode) return new Set<string>()
|
||||||
const set = new Set<string>()
|
const set = new Set<string>()
|
||||||
@@ -157,15 +157,11 @@ export function GraphView({ data, selectedNodeId, onNodeSelect }: {
|
|||||||
backgroundColor="rgba(0,0,0,0)"
|
backgroundColor="rgba(0,0,0,0)"
|
||||||
cooldownTicks={120}
|
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) => {
|
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.fillStyle = color
|
||||||
ctx.beginPath()
|
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()
|
ctx.fill()
|
||||||
}}
|
}}
|
||||||
|
|
||||||
@@ -239,16 +235,26 @@ export function GraphView({ data, selectedNodeId, onNodeSelect }: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. Zeichne das Label nur unter bestimmten Bedingungen (Zoom-abhängig)
|
// 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))
|
const showLabel = isHovered || isSelected || (globalScale >= 1.25) || (activeNode && connectedNodes.has(node.id))
|
||||||
|
|
||||||
if (showLabel) {
|
if (showLabel) {
|
||||||
const label = node.content.length > 35 ? node.content.slice(0, 34) + "…" : node.content
|
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 baseFontSize = isHovered || isSelected ? 10.5 : 9.5
|
||||||
const fontSize = baseFontSize / Math.max(0.5, globalScale)
|
const fontSize = baseFontSize / Math.max(0.5, globalScale)
|
||||||
|
|
||||||
ctx.font = `${isHovered || isSelected ? "bold" : "500"} ${fontSize}px "Space Grotesk", sans-serif`
|
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)
|
const bpad = 4.5 / Math.max(0.5, globalScale)
|
||||||
|
|
||||||
ctx.fillStyle = isDimmed ? "rgba(10, 15, 25, 0.45)" : "rgba(8, 12, 20, 0.88)"
|
ctx.fillStyle = isDimmed ? "rgba(10, 15, 25, 0.45)" : "rgba(8, 12, 20, 0.88)"
|
||||||
|
|||||||
Reference in New Issue
Block a user