Files
wled-screen-controller-mixed/contexts/graph-editor.md
alexei.dolgolyov 191c988cf9 Graph node FPS hover tooltip, full names, no native SVG tooltips
Graph editor:
- Floating FPS tooltip on hover over running output_target nodes (300ms delay)
- Shows errors, uptime, and FPS sparkline seeded from server metrics history
- Tooltip positioned below node with fade-in/out animation
- Uses pointerover/pointerout with relatedTarget check to prevent flicker
- Fixed-width tooltip (200px) with monospace values to prevent layout shift
- Node titles show full names (removed truncate), no native SVG <title> tooltips

Documentation:
- Added duration/numeric formatting conventions to contexts/frontend.md
- Added node hover tooltip docs to contexts/graph-editor.md

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 15:45:59 +03:00

5.6 KiB

Visual Graph Editor

Read this file when working on the graph editor (static/js/features/graph-editor.js and related modules).

Architecture

The graph editor renders all entities (devices, templates, sources, clocks, targets, scenes, automations) as SVG nodes connected by edges in a left-to-right layered layout.

Core modules

File Responsibility
js/features/graph-editor.js Main orchestrator — toolbar, keyboard, search, filter, add-entity menu, port/node drag, minimap
js/core/graph-layout.js ELK.js layout, buildGraph(), computePorts(), entity color/label maps
js/core/graph-nodes.js SVG node rendering, overlay buttons, per-node color overrides
js/core/graph-edges.js SVG edge rendering (bezier curves, arrowheads, flow dots)
js/core/graph-canvas.js Pan/zoom controller with zoomToPoint() rAF animation
js/core/graph-connections.js CONNECTION_MAP — which fields link entity types, drag-connect/detach logic
css/graph-editor.css All graph-specific styles

Data flow

  1. loadGraphEditor()_fetchAllEntities() fetches all caches in parallel
  2. computeLayout(entities) builds ELK graph, runs layout → returns {nodes: Map, edges: Array, bounds}
  3. computePorts(nodeMap, edges) assigns port positions and annotates edges with fromPortY/toPortY
  4. Manual position overrides (_manualPositions) applied after layout
  5. renderEdges() + renderNodes() paint SVG elements
  6. GraphCanvas handles pan/zoom via CSS transform: scale() translate()

Edge rendering

Edges always use _defaultBezier() (port-aware cubic bezier) — ELK edge routing is ignored because it lacks port awareness, causing misaligned bend points. ELK is only used for node positioning.

Port system

Nodes have input ports (left) and output ports (right), colored by edge type. Port types are ordered vertically: template > picture > colorstrip > value > audio > clock > scene > device > default.

Keeping the graph in sync with entity types

CRITICAL: When adding or modifying entity types in the system, these graph files MUST be updated:

Adding a new entity type

  1. graph-layout.jsENTITY_COLORS, ENTITY_LABELS, buildGraph() (add node loop + edge loops)
  2. graph-layout.jsedgeType() function if the new type needs a distinct edge color
  3. graph-nodes.jsKIND_ICONS (default icon), SUBTYPE_ICONS (subtype-specific icons)
  4. graph-nodes.jsSTART_STOP_KINDS or TEST_KINDS sets if the entity supports start/stop or test
  5. graph-connections.jsCONNECTION_MAP for drag-connect edge creation
  6. graph-editor.jsADD_ENTITY_MAP (add-entity menu entry with window function)
  7. graph-editor.jsALL_CACHES array (for new-entity-focus watcher)
  8. graph-editor.js_fetchAllEntities() (add cache fetch + pass to computeLayout)
  9. core/state.js — Add/export the new DataCache
  10. app.js — Import and window-export the add/edit/clone functions

Adding a new field/connection to an existing entity

  1. graph-layout.jsbuildGraph() edges section: add addEdge() call
  2. graph-connections.jsCONNECTION_MAP: add the field entry
  3. graph-edges.jsEDGE_COLORS if a new edge type is needed

Adding a new entity subtype

  1. graph-nodes.jsSUBTYPE_ICONS[kind] — add icon for the new subtype
  2. graph-layout.jsbuildGraph() — ensure subtype is extracted from the entity data

Features & keyboard shortcuts

Key Action
/ Open search
F Toggle filter
F11 Toggle fullscreen
+ Add entity menu
Escape Close filter → close search → deselect all
Delete Delete selected edge or node
Arrows / WASD Spatial navigation between nodes
Ctrl+A Select all nodes

Node color overrides

Per-node colors stored in localStorage key graph_node_colors. The getNodeColor(nodeId, kind) function returns the override or falls back to ENTITY_COLORS[kind]. The color bar on the left side of each node is clickable to open a native color picker.

Filter system

The filter bar (toggled with F or toolbar button) filters nodes by name/kind/subtype. Non-matching nodes get the .graph-filtered-out CSS class (low opacity, no pointer events). Edges where either endpoint is filtered also dim. Minimap nodes for filtered-out entities become nearly invisible (opacity 0.07).

Minimap

Rendered as a small SVG with colored rects for each node and a viewport rect. Supports drag-to-pan, resize handles, and position persistence in localStorage.

Node hover FPS tooltip

Running output_target nodes show a floating HTML tooltip on hover (300ms delay). The tooltip is an absolutely-positioned <div class="graph-node-tooltip"> inside .graph-container (not SVG — needed for Chart.js canvas). It displays errors, uptime, and a FPS sparkline (reusing createFpsSparkline from core/chart-utils.js). The sparkline is seeded from /api/v1/system/metrics-history for instant context.

Hover events use pointerover/pointerout with relatedTarget check to prevent flicker when the cursor moves between child SVG elements within the same <g> node.

Node titles display the full entity name (no truncation). Native SVG <title> tooltips are omitted on nodes to avoid conflict with the custom tooltip.

New entity focus

When a user adds an entity via the graph's + menu, a watcher subscribes to all caches, detects the new ID, reloads the graph, and uses zoomToPoint() to smoothly fly to the new node with zoom + highlight animation.