Use this track when the canvas is primarily a graph editor, workflow builder, schema diagram, dependency map, or node-and-connector application.
The shorter Add Graph Connections guide shows how graph support fits into a
canvas. This track goes deeper: node modeling, port declarations, connector
layers, connection gestures, reconnect UX, routing, labels, hit targets, layout,
navigation, persistence, shared canvases, and recipes.
What LivePanels Gives A Graph App
LivePanels gives graph apps a server-authoritative canvas model:
- graph nodes are canvas items;
- ports are declared item capabilities;
- connectors live in canvas state;
- connection gestures become commands;
- connector projection derives SVG paths from item placement and port geometry;
- selection, history, clipboard, persistence, and shared mode keep working because graph state stays inside the canvas model.
Your application still owns the product:
- what a node means;
- what a port means;
- how nodes and ports look;
- how connectors are styled;
- whether a connection creates a domain record;
- how users browse, filter, inspect, and edit the graph.
The Graph App Loop
A graph app usually follows this loop:
author node items
-> declare ports on those item attrs
-> render application node markup
-> render application port controls with graph attrs
-> let canvas_runtime render connector items in the world SVG projection
-> style runtime connector classes and data attrs
-> send graph commands from browser attrs or server codeThe important part is that the graph does not become a separate client-side store. The graph is a capability on the canvas.
Minimum Graph Canvas
Start with the graph toolkit:
defmodule MyAppWeb.FlowLive do
use MyAppWeb, :live_view
use LivePanels.Canvas,
pubsub: MyApp.PubSub
use LivePanels.Toolkit.Graph
def render(assigns) do
~H"""
<.canvas_runtime {canvas_runtime_attrs(assigns)}>
<:item
id="step-1"
type="step"
at={{120, 80}}
size={{520, 360}}
ports={[
%{name: "in", direction: :input, type: :task},
%{name: "out", direction: :output, type: :task}
]}
>
<.step_card />
</:item>
</.canvas_runtime>
"""
end
endRender the canvas and node ports. Connector items render through the runtime world SVG item projection:
<.canvas_runtime {canvas_runtime_attrs(assigns)}>
<:item :let={context}>
<article class="node">
<header {drag_handle_attrs()}>{context.item.label}</header>
<button
:for={port <- context.ports}
{graph_port_anchor_attrs(port)}
{if port.direction == :output,
do: begin_connector_attrs(context.item, port),
else: complete_connector_attrs(context.item, port)}
>
{port.label}
</button>
{context.content}
</article>
</:item>
</.canvas_runtime>That is the foundation. The rest of this track shows how to turn it into a real graph application.
Track Map
Read the guides in this order when building a graph app:
Model Nodes, Ports, And Connectorsfor the runtime data model.Render Nodes And Portsfor item shells, port placement, and application UI.Render Connector Itemsfor runtime SVG connector rendering and styling.Connect, Reconnect, And Disconnectfor graph commands and gestures.Select, Edit, Route, Label, And Hit-Test Connectorsfor graph editor behavior.Layout, Fit, And Navigate Graphsfor graph layout and viewport movement.Persist And Share Graph Canvasesfor recovery and collaboration.Graph App Recipesfor common application patterns.
What To Keep Out Of The Core Runtime
Do not put product meaning into LivePanels.
A connection might mean "foreign key", "workflow transition", "message route", "dependency", "lineage", or "related idea". LivePanels only needs to know that a connector connects endpoint A to endpoint B and that the command is valid.
Keep domain records in your app. Use LivePanels to make the canvas behavior consistent.