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>
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
loadGraphEditor()→_fetchAllEntities()fetches all caches in parallelcomputeLayout(entities)builds ELK graph, runs layout → returns{nodes: Map, edges: Array, bounds}computePorts(nodeMap, edges)assigns port positions and annotates edges withfromPortY/toPortY- Manual position overrides (
_manualPositions) applied after layout renderEdges()+renderNodes()paint SVG elementsGraphCanvashandles pan/zoom via CSStransform: 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
graph-layout.js—ENTITY_COLORS,ENTITY_LABELS,buildGraph()(add node loop + edge loops)graph-layout.js—edgeType()function if the new type needs a distinct edge colorgraph-nodes.js—KIND_ICONS(default icon),SUBTYPE_ICONS(subtype-specific icons)graph-nodes.js—START_STOP_KINDSorTEST_KINDSsets if the entity supports start/stop or testgraph-connections.js—CONNECTION_MAPfor drag-connect edge creationgraph-editor.js—ADD_ENTITY_MAP(add-entity menu entry with window function)graph-editor.js—ALL_CACHESarray (for new-entity-focus watcher)graph-editor.js—_fetchAllEntities()(add cache fetch + pass tocomputeLayout)core/state.js— Add/export the new DataCacheapp.js— Import and window-export the add/edit/clone functions
Adding a new field/connection to an existing entity
graph-layout.js—buildGraph()edges section: addaddEdge()callgraph-connections.js—CONNECTION_MAP: add the field entrygraph-edges.js—EDGE_COLORSif a new edge type is needed
Adding a new entity subtype
graph-nodes.js—SUBTYPE_ICONS[kind]— add icon for the new subtypegraph-layout.js—buildGraph()— ensuresubtypeis 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.