# Diagram

> Nodes joined by lines on a canvas: drawn, selected, moved to another port and removed, by any hand.

A diagram is nodes joined by lines, arranged on a plane: a pipeline, a flow, a
graph of dependencies, a node editor. It is a [canvas](https://ui.publictheta.com/components/canvas.md)
whose things have ports, with lines between them that can be drawn, selected,
moved and removed.

## When to use

Use a diagram when the connections are what people are making. When things only
have places, a [canvas](https://ui.publictheta.com/components/canvas.md) is enough; when they only have an
order, a [listbox](https://ui.publictheta.com/components/listbox.md) is.

## Why it is a component of its own

A canvas does not know about lines, and should not: most canvases have none.
But a line is not decoration. It can be selected, has a name, takes focus,
answers a pointer, and is exposed to assistive technology — and each of those is
a statement that has to be checked against something. Left to each application,
the parts hardest to get right would be the ones written again every time: what
a screen reader is told about a connection, how a line is drawn without a
pointer, how an end is moved to another port.

So the diagram adds nothing of its own but the wiring. The plane, and all that
is done to a node on it, is the [canvas](https://ui.publictheta.com/components/canvas.md). The connections
as a list are a [listbox](https://ui.publictheta.com/components/listbox.md). The gesture is
[connecting](https://ui.publictheta.com/foundations/connect.md). What may be joined, what a change is and
where a layout puts things is the [graph](https://ui.publictheta.com/foundations/graph.md). Where a line
runs, and what a pointer picks, is an [algorithm](https://ui.publictheta.com/algorithms/edge-route.md).

## Best practices

**Draw lines that read.** A line leaves its port squarely, goes round the two
nodes it joins rather than through them — and round the other nodes in its
way, even one that is being dragged there — turns half-way between them, and
arrives squarely, with an arrowhead where it arrives. Rounded corners or
curves: choose one and keep to it.

**Keep lines with their nodes.** While a node is dragged its lines follow at
every moment, not when it is dropped.

**Make a line easy to hit.** A line is a hair wide and a pointer is not that
exact: a click within a few pixels of a line is a click on it, and a finger is
given more. A line under a resting mouse shows that it can be picked.

**Select lines with nodes.** A line is selected as a node is, and with nodes:
click, Shift-click, tap. One Delete removes whatever is selected. A node's
lines go with it, as one change to undo.

**Join by dragging, by two presses, or by keys.** See
[Connecting](https://ui.publictheta.com/foundations/connect.md). Show which ports can take a line the moment
it is begun, and refuse in the open, with the reason.

**Move an end without redrawing the line.** A selected line shows, a little way
in from each end, a part to take that end by. Dragged to another port, the same
line now ends there. Let go anywhere else, it stays where it was.

**Let go over nothing, offer what could be there.** A line let go over empty
space is not simply given up: offer, at that place, the kinds of node that could
take it, and add the chosen one already joined. Offer only what can take the
line; with nothing to offer, let it go quietly.

**Drop a node onto a line to put it in.** A node dragged on its own until its
middle lies on a line it can take and give on shows that line, and dropped
there is put into it: the line becomes two, through the node. The move and the
splice are one change, taken back by one undo. A node that cannot take what the
line carries only moves — and the line goes round it.

**Label a line where it says something.** What a connection carries, or when, is
written half-way along it, moves with it, and is read with the connection's
name in the list.

**Offer layout, and say whether it stays on.** A button that lays the diagram
out once, and a setting that keeps it laid out as it changes, are different
promises. Let a node be pinned against both. See [Graphs](https://ui.publictheta.com/foundations/graph.md).

**Stay immediate among thousands.** Draw only the lines in and around the view;
work out a line again only when one of its two nodes has moved.

## Conventions

Everything a [canvas](https://ui.publictheta.com/components/canvas.md) does for a thing, it does for a node.
A diagram adds:

| Intent                  | Pointer                                   | Keyboard                                | Touch                       |
| ----------------------- | ----------------------------------------- | --------------------------------------- | --------------------------- |
| Reach a port            | —                                         | Tab from its node; Shift-Tab back       | —                           |
| Join two ports          | Drag from one to the other; or click each | Enter on a port, arrows, Enter          | Drag; or tap each           |
| Give up a connection    | Escape; release elsewhere                 | Escape                                  | Lift elsewhere              |
| Select a line           | Click it; Shift-click to add              | Space on it, in the list of connections | Tap it                      |
| Reach the connections   | —                                         | Tab past the last node; then arrow keys | —                           |
| Move a line's end       | Drag the part near that end               | Enter on the connection, arrows, Enter  | Drag the part near that end |
| Add a node joined       | Let a line go over empty space; choose    | —                                       | The same                    |
| Put a node into a line  | Drag the node onto the line               | —                                       | The same                    |
| Remove what is selected | —                                         | Delete, Backspace                       | —                           |

## Platform considerations

**Keyboard.** Tab goes from a node through its ports and on to the next node,
in the canvas's reading order; after the last node come the connections, as one
stop, and then Tab leaves. In that list the arrow keys go from one connection
to the next, and the line the keyboard is on is ringed and brought into view,
since the list itself is not seen. Space selects it, Delete removes it, and
Enter takes hold of the end it arrives at. While a connection is being made the
arrow keys and Tab are its own, and Escape gives it up without clearing the
selection.

**Touch.** A port is small on the screen and a fingertip is not: a port's
target, and that of the part a line's end is taken by, is at least 44 pixels
across, at any zoom. A tap on a port begins a connection and a tap on another
ends it. A finger's aim at a line, and its reach for a port, are wider than a
mouse's.

**Pointer.** A secondary press on a line selects it unless it is already
selected, so that a menu opened there is about what it appears over.

**Right to left.** As on a canvas, the plane does not mirror. A layout's
direction is the diagram's meaning — a pipeline that reads left to right — and
is the application's to set.

**Forced colours.** Lines, the line being drawn, and the ring of a line the
keyboard is on are drawn in the system's text and highlight colours.

**Assistive technology.** A line cannot be seen by someone who cannot see, so
it is said three times over. Each node says what it sends to and what it
receives from. Each port is a button, named for what it is, that says what is
joined to it. And the connections are a list after the nodes, each named by its
two ends, where one can be selected, removed, or have its end moved. What
cannot be seen happening is said: that a connection was begun and how many
ports can take it, that it was made, why it was not, what was removed.

## Behaviour

Each statement is verified by a test in Chromium, Chromium (touch), Firefox, WebKit, WebKit (touch), unless it names the environments it applies to or is marked open.

### Semantics

- The diagram is an application with a name, and says how its keys work, ports and connections among them.
- Each node is a group with its name, and says what it sends to and what it receives from.
- Each port is a button named for what it is, and says what it is connected to.
- The connections are a list after the nodes, each named by its two ends, and one that is selected says so.
- A diagram with no connections has no list of them.
- It has no accessibility violations, with a node and a connection selected.
- Where the system forces its colours, lines and ports take the system's, and each state of a port is still told apart. *(Chromium, Chromium (touch))*

### Lines

- A line runs from the port it leaves to the port it arrives at.
- A line that cannot run straight goes round by level and upright runs, with its corners rounded.
- A line goes round the other nodes in its way, not through them.
- A node dragged into a line's way is gone round while it is dragged, and where it is let go.
- A line follows its nodes while they are dragged, and stays with them when the drag ends.
- Lines are drawn on the plane: they move and scale with it, beneath the nodes.
- A hundred million units from zero, a line is drawn where its nodes are, in small numbers.
- Among a thousand connections, only those in and around the view are drawn, and those that are selected.
- A line whose node is not there is not drawn, and does no harm.

### Selecting lines

- A click on a line selects it, and only it; a click on a node, or on empty space, lets it go.
- A click a few pixels from a line still picks it; further off, it is a click on empty space. *(Chromium, Firefox, WebKit)*
- Shift-click adds a line to what is selected, or takes it out, nodes and lines together.
- Command-click adds a line, or takes it out, as Shift-click does. *(Chromium, Firefox, WebKit)*
- A tap on a line adds it to what is selected or takes it out, and a finger need not be as exact. *(Chromium (touch))*
- A line under the mouse shows that it can be picked. *(Chromium, Firefox, WebKit)*
- A secondary press on a line selects it, unless it is selected already. *(Chromium, Firefox, WebKit)*

### Removing

- Delete removes what is selected and says so; a node's lines go with it, in the same change.
- Backspace removes a selected line alone, and its nodes stay.
- In a field inside a node, Delete and Backspace are the field's.
- A locked node is not removed, though it is selected.

### Connecting

- A drag from a port to a port that takes it joins them with a line, and says so.
- A line drawn from the port that takes is recorded running the right way.
- While it is drawn the line runs from its port to the pointer, and every port says whether it can take it.
- Released on a port that cannot take it, nothing is joined: the port and the line show it, and the reason is said and told.
- A connection already made is not made twice, and where loops are refused, one that would close a loop is not made.
- Escape while a line is being drawn takes it back.
- A press on a port does not move its node, select it, or begin a marquee.
- A click on a port and then on another joins them: nothing need be held down.
- A tap on a port and then on another joins them, and a port's target is 44 pixels across. *(Chromium (touch), WebKit (touch))*
- A line drawn to the edge of the view moves the view along, and can end on a port that was out of view.
- However long a frame takes, the view stops with the port still under the pointer.

### Moving an end

- A selected line shows, near each end, a part to take that end by.
- On a line too short for both, the two parts keep apart: each three-tenths of the way from its end.
- Dragging that part to another port moves the end there, as one change that keeps the line.
- While an end is moved the line is drawn from the end that stays, and the line as it was is faint.
- An end let go over nothing, or over a port that cannot take it, stays where it was.
- Under a finger, the part an end is taken by has a target 44 pixels across. *(Chromium (touch), WebKit (touch))*

### Keyboard and focus

- Tab goes from a node through its ports to the next node; past the last, to the connections, and then on out.
- Shift-Tab goes back through a node's ports to the node, and from a node to the node before.
- Enter on a port begins a connection and says so; focus goes to the nearest port that can take it.
- The arrow keys choose among the ports that can take it, and move nothing; Enter joins.
- Escape gives it up and returns to the port it began on; the selection is as it was.
- From a port with nowhere to go, Enter begins nothing, and the reason is said.

#### The connections

- In the list the arrow keys go from one connection to the next, and the line the keyboard is on is shown.
- Space selects the connection the keyboard is on, or takes it out, and leaves the nodes that are selected.
- Delete removes the connection the keyboard is on, and focus stays in the list.
- When the last connection is removed, focus goes to the nodes.
- Enter takes hold of the end a connection arrives at: the arrow keys choose another port, and Enter moves it there.
- Escape while an end is held gives it up, and returns to the list.
- A connection the keyboard reaches is brought into view.
- Among ten thousand connections, the list holds only those near the one the keyboard is on. *(open)*

### Putting a node into a line

- A node dragged onto a line that it can take and give on shows the line it would go into; let go, it is put into it, as one change.
- A node dragged near a line but not onto it, or onto a line of its own, is only moved.

### Labels

- A connection's label is shown half-way along its line.
- In the list of connections, a connection is read with its label.
- A label goes with its line while a node is dragged.

## Reference implementation

React: `npx shadcn@latest add https://ui.publictheta.com/r/diagram.json`
