Graph
tier 3 — a surfaceRecords and the links between them, drawn on a canvas: a dependency map, an org chart, a network, a knowledge graph, a flow. The app writes the records as a list and the library draws the list. The canvas is the one thing it paints — the toolbar, the legend, the empty state and every word around the drawing are the app's own markup with the classes on this page.
Sedna.UI.js finds every [data-graph] and imports the engine the first time
one comes within a screen of the viewport, so a page without a graph never downloads it. This page is
the anatomy; Graph layouts places the records,
Exploring a graph filters, searches and explains them, and
Graph in Blazor feeds them from C#. Every attribute, control, event and
member is in docs/graph.md.
A graph from a list
The least a graph needs: a .graph with data-graph, an id and an
aria-label; its records as a .graph-data list; a .graph-canvas to
draw into; and a .graph-wait covering it until the first drawing is ready, because a canvas
that fills in record by record reads as broken. data-node makes an item a record, and
data-edge with data-source and data-target makes it a link.
The list is the data and the text alternative. It is visually hidden and a screen
reader reads it, so each link's text is a sentence — “The orders API writes the order to
the orders database” — and data-label is the short form the canvas writes. A
record's text is its name. Point at a record to light what it touches and read its links. When the list
changes, the drawing changes with it: what stayed keeps its place, and what arrived is put beside what
it links to. With scripting off, the graph is shown as its list.
- Storefront
- Orders API
- Payments
- Orders database
- Stock
- Warehouse
- Order mails
- The storefront places the order with the orders API
- The orders API charges the customer through payments
- The orders API writes the order to the orders database
- The orders API reserves the items in stock
- Stock asks the warehouse to pick the items
- The orders API confirms the order by mail
- Payments sends the receipt by mail
<div class="graph" data-graph id="graph-order-path" data-graph-arrows="target" aria-label="How an order travels through the shop">
<ul class="graph-data" data-graph-data>
<li data-node="storefront" data-tone="1" data-icon="ri-store-2-line">Storefront</li>
<li data-node="orders-api" data-tone="2" data-icon="ri-server-line">Orders API</li>
<li data-node="payments" data-tone="3" data-icon="ri-bank-card-line">Payments</li>
<li data-node="orders-db" data-tone="4" data-icon="ri-database-2-line">Orders database</li>
<li data-node="stock" data-tone="5" data-icon="ri-stack-line">Stock</li>
<li data-node="warehouse" data-tone="5" data-icon="ri-building-3-line">Warehouse</li>
<li data-node="mail" data-tone="6" data-icon="ri-mail-send-line">Order mails</li>
<li data-edge data-source="storefront" data-target="orders-api" data-label="places">The storefront places the order with the orders API</li>
<li data-edge data-source="orders-api" data-target="payments" data-label="charges">The orders API charges the customer through payments</li>
<li data-edge data-source="orders-api" data-target="orders-db" data-label="writes">The orders API writes the order to the orders database</li>
<li data-edge data-source="orders-api" data-target="stock" data-label="reserves">The orders API reserves the items in stock</li>
<li data-edge data-source="stock" data-target="warehouse" data-label="picks">Stock asks the warehouse to pick the items</li>
<li data-edge data-source="orders-api" data-target="mail" data-label="confirms">The orders API confirms the order by mail</li>
<li data-edge data-source="payments" data-target="mail" data-label="receipt">Payments sends the receipt by mail</li>
</ul>
<div class="graph-canvas"></div>
<div class="graph-wait skeleton skeleton-block"></div>
</div>
The whole frame
.graph-frame with data-graph-frame holds a graph and what goes with it: a
.toolbar above, a .graph-legend below, a panel beside. A control anywhere in
the frame drives its graph without naming it, and full screen takes the whole frame, so the search and
the legend come along. A graph in a .card takes the card's corners, and its names are
outlined in the card's colour rather than the page's.
.graph-tools is the view's own toolbar on the canvas: ghost icon buttons, each with a
data-graph-action, an aria-label and a data-tip, in groups split by
.graph-tools-sep. Full screen is a toggle the script flips through
aria-pressed; write both of its icons, .graph-when-off and
.graph-when-on, and the matching one shows, so nothing rewrites the button.
canvas.graph-minimap is the whole drawing in miniature — press it to go there —
and is hidden on a phone. .graph-empty on an .empty-state is what shows when
there are no records at all, and data-graph-stats keeps a count current in the app's words.
Each .graph-key in the legend holds a .graph-swatch wearing the
.series-* class of the records' data-tone — the colour the canvas paints
that tone, in every theme — or a .graph-line for a kind of link.
- Storefront
- Catalogue
- Search
- Cart
- Checkout
- Payments
- Fraud check
- Ledger
- Invoices
- Orders
- Stock
- Warehouse
- Shipping
- Returns
- Identity
- Notifications
- Audit log
- Feature flags
- The storefront lists products from the catalogue
- The storefront searches with search
- Search indexes the catalogue
- The storefront fills the cart
- The cart hands over to checkout
- Checkout charges through payments
- Checkout places the order
- Payments screens each charge with the fraud check
- Payments books each charge in the ledger
- Orders bills through invoices
- Invoices books each invoice in the ledger
- Orders reserves the items in stock
- Stock asks the warehouse to pick
- The warehouse hands the parcel to shipping
- Returns puts items back in stock
- Returns refunds through payments
- The storefront signs customers in with identity
- Checkout verifies the customer with identity
- Orders announces each change through notifications
- Shipping announces each parcel through notifications
- Payments records every charge in the audit log
- Identity records every sign-in in the audit log
- Checkout reads its feature flags
- The storefront reads its feature flags
A service appears here once its first deployment reports in.
<div class="graph-frame" data-graph-frame>
<div class="toolbar">
<div class="toolbar-filters">
<input class="form-input toolbar-input toolbar-input--wide" type="search" placeholder="Find a service"
aria-label="Find a service" data-graph-search />
</div>
<span class="toolbar-count" data-graph-stats="{nodes} services · {edges} links"
data-graph-stats-match="{matches} of {nodes} services match"></span>
</div>
<div class="card">
<div class="graph" data-graph id="graph-platform" data-graph-arrows="target"
aria-label="Platform services and the calls between them">
<ul class="graph-data" data-graph-data>
<li data-node="storefront" data-tone="1" data-icon="ri-store-2-line" data-meta="Commerce">Storefront</li>
<li data-node="catalogue" data-tone="1" data-icon="ri-book-shelf-line" data-meta="Commerce">Catalogue</li>
<li data-node="search" data-tone="1" data-icon="ri-search-line" data-meta="Commerce">Search</li>
<li data-node="cart" data-tone="1" data-icon="ri-shopping-cart-2-line" data-meta="Commerce">Cart</li>
<li data-node="checkout" data-tone="1" data-icon="ri-secure-payment-line" data-meta="Commerce">Checkout</li>
<li data-node="payments" data-tone="2" data-icon="ri-bank-card-line" data-meta="Payments">Payments</li>
<li data-node="fraud" data-tone="2" data-icon="ri-shield-check-line" data-meta="Payments">Fraud check</li>
<li data-node="ledger" data-tone="2" data-icon="ri-book-2-line" data-meta="Payments">Ledger</li>
<li data-node="invoices" data-tone="2" data-icon="ri-bill-line" data-meta="Payments">Invoices</li>
<li data-node="orders" data-tone="3" data-icon="ri-file-list-3-line" data-meta="Fulfilment">Orders</li>
<li data-node="stock" data-tone="3" data-icon="ri-stack-line" data-meta="Fulfilment">Stock</li>
<li data-node="warehouse" data-tone="3" data-icon="ri-building-3-line" data-meta="Fulfilment">Warehouse</li>
<li data-node="shipping" data-tone="3" data-icon="ri-truck-line" data-meta="Fulfilment">Shipping</li>
<li data-node="returns" data-tone="3" data-icon="ri-arrow-go-back-line" data-meta="Fulfilment">Returns</li>
<li data-node="identity" data-tone="4" data-icon="ri-fingerprint-line" data-meta="Platform">Identity</li>
<li data-node="notify" data-tone="4" data-icon="ri-notification-3-line" data-meta="Platform">Notifications</li>
<li data-node="audit" data-tone="4" data-icon="ri-file-shield-2-line" data-meta="Platform">Audit log</li>
<li data-node="flags" data-tone="4" data-icon="ri-toggle-line" data-meta="Platform">Feature flags</li>
<li data-edge data-source="storefront" data-target="catalogue" data-label="lists">The storefront lists products from the catalogue</li>
<li data-edge data-source="storefront" data-target="search" data-label="searches">The storefront searches with search</li>
<li data-edge data-source="search" data-target="catalogue" data-label="indexes" data-line="dashed">Search indexes the catalogue</li>
<li data-edge data-source="storefront" data-target="cart" data-label="fills">The storefront fills the cart</li>
<li data-edge data-source="cart" data-target="checkout" data-label="hands over">The cart hands over to checkout</li>
<li data-edge data-source="checkout" data-target="payments" data-label="charges">Checkout charges through payments</li>
<li data-edge data-source="checkout" data-target="orders" data-label="places">Checkout places the order</li>
<li data-edge data-source="payments" data-target="fraud" data-label="screens">Payments screens each charge with the fraud check</li>
<li data-edge data-source="payments" data-target="ledger" data-label="books">Payments books each charge in the ledger</li>
<li data-edge data-source="orders" data-target="invoices" data-label="bills" data-line="dashed">Orders bills through invoices</li>
<li data-edge data-source="invoices" data-target="ledger" data-label="books">Invoices books each invoice in the ledger</li>
<li data-edge data-source="orders" data-target="stock" data-label="reserves">Orders reserves the items in stock</li>
<li data-edge data-source="stock" data-target="warehouse" data-label="picks">Stock asks the warehouse to pick</li>
<li data-edge data-source="warehouse" data-target="shipping" data-label="hands over">The warehouse hands the parcel to shipping</li>
<li data-edge data-source="returns" data-target="stock" data-label="restocks">Returns puts items back in stock</li>
<li data-edge data-source="returns" data-target="payments" data-label="refunds">Returns refunds through payments</li>
<li data-edge data-source="storefront" data-target="identity" data-label="signs in">The storefront signs customers in with identity</li>
<li data-edge data-source="checkout" data-target="identity" data-label="verifies">Checkout verifies the customer with identity</li>
<li data-edge data-source="orders" data-target="notify" data-label="announces" data-line="dashed">Orders announces each change through notifications</li>
<li data-edge data-source="shipping" data-target="notify" data-label="announces" data-line="dashed">Shipping announces each parcel through notifications</li>
<li data-edge data-source="payments" data-target="audit" data-label="records" data-line="dashed">Payments records every charge in the audit log</li>
<li data-edge data-source="identity" data-target="audit" data-label="records" data-line="dashed">Identity records every sign-in in the audit log</li>
<li data-edge data-source="checkout" data-target="flags" data-label="reads">Checkout reads its feature flags</li>
<li data-edge data-source="storefront" data-target="flags" data-label="reads">The storefront reads its feature flags</li>
</ul>
<div class="graph-canvas"></div>
<div class="graph-tools" role="toolbar" aria-label="View">
<button class="btn btn--ghost btn--sm btn--icon" type="button" data-graph-action="zoom-in"
aria-label="Zoom in" data-tip="Zoom in"><i class="ri-zoom-in-line"></i></button>
<button class="btn btn--ghost btn--sm btn--icon" type="button" data-graph-action="zoom-out"
aria-label="Zoom out" data-tip="Zoom out"><i class="ri-zoom-out-line"></i></button>
<button class="btn btn--ghost btn--sm btn--icon" type="button" data-graph-action="fit"
aria-label="Fit" data-tip="Fit everything"><i class="ri-focus-3-line"></i></button>
<span class="graph-tools-sep" aria-hidden="true"></span>
<button class="btn btn--ghost btn--sm btn--icon" type="button" data-graph-action="arrange"
aria-label="Arrange" data-tip="Arrange again"><i class="ri-magic-line"></i></button>
<span class="graph-tools-sep" aria-hidden="true"></span>
<button class="btn btn--ghost btn--sm btn--icon" type="button" data-graph-action="fullscreen"
aria-pressed="false" aria-label="Full screen" data-tip="Full screen">
<i class="ri-fullscreen-line graph-when-off"></i>
<i class="ri-fullscreen-exit-line graph-when-on"></i>
</button>
</div>
<canvas class="graph-minimap" data-graph-minimap aria-hidden="true"></canvas>
<div class="graph-wait skeleton skeleton-block"></div>
<div class="graph-empty empty-state">
<svg class="state-art" aria-hidden="true"><use href="#nothing-yet" /></svg>
<span class="empty-state-title">No services registered</span>
<p>A service appears here once its first deployment reports in.</p>
</div>
</div>
</div>
<div class="graph-legend">
<span class="graph-key"><span class="graph-swatch series-1"></span><span>Commerce</span></span>
<span class="graph-key"><span class="graph-swatch series-2"></span><span>Payments</span></span>
<span class="graph-key"><span class="graph-swatch series-3"></span><span>Fulfilment</span></span>
<span class="graph-key"><span class="graph-swatch series-4"></span><span>Platform</span></span>
<span class="graph-key"><span class="graph-line graph-line--arrow"></span><span>Calls, and waits for the answer</span></span>
<span class="graph-key"><span class="graph-line graph-line--dashed graph-line--arrow"></span><span>Sends, and carries on</span></span>
</div>
</div>
Records
What a record says about itself. data-tone is its colour: 1 to 6
are the categorical run every chart uses, for records that are merely different;
go, warn, danger, info and muted are for
a record whose colour means something. data-shape and data-icon say
what kind of record it is, data-weight how large it is drawn — by default, how many
links it has — and data-meta is a second line for its tooltip.
data-root is what the graph is about: ringed, always named, and where the keyboard starts.
data-hub is named in bold and before the others. data-muted is drawn quieter,
because a superseded decision still explains how the project got here. Names are placed the way a map
places them — the most important first, and one that would run into a name already placed waits
until zooming in makes room.
Colour is never the only carrier. Each kind here has a shape and a colour at once, so
the legend still reads in every colour-vision setting. A .graph-swatch takes the shape as
well: --rounded, --hexagon, --square, --star,
--tag, --diamond and --triangle, and --muted for a
quieter record.
- Checkout rebuild
- Alex Fischer
- Priya Nair
- Jordan Weiss
- Rin Nakamura
- Web team
- Payments team
- Design brief
- Rollout runbook
- Load test report
- Staff beta
- General release
- pci
- q4
- One checkout page
- Tokenise cards at the edge
- Three-step checkout
- Card provider cut-over
- Holiday change freeze
- Alex Fischer leads the checkout rebuild
- Jordan Weiss owns the checkout rebuild
- The web team builds the checkout rebuild
- The payments team builds the checkout rebuild
- Alex Fischer is in the web team
- Rin Nakamura is in the web team
- Priya Nair is in the payments team
- Rin Nakamura wrote the design brief
- The design brief describes the checkout rebuild
- Alex Fischer wrote the rollout runbook
- The rollout runbook prepares the general release
- The load test report gates the staff beta
- Priya Nair ran the load test
- The staff beta is the next milestone
- The general release follows the staff beta
- Jordan Weiss proposed one checkout page
- One checkout page was decided for the rebuild
- One checkout page supersedes the three-step checkout
- Priya Nair proposed tokenising cards at the edge
- Tokenising cards at the edge was decided for the rebuild
- Tokenising cards at the edge mitigates the card provider cut-over
- The card provider cut-over blocks the general release
- The holiday change freeze threatens the general release
- The checkout rebuild is tagged pci
- Tokenising cards at the edge is tagged pci
- The staff beta is tagged q4
- The general release is tagged q4
<div class="graph-frame">
<div class="graph" data-graph id="graph-project" data-graph-spacing="loose" aria-label="Everything linked to the checkout rebuild">
<ul class="graph-data" data-graph-data>
<li data-node="rebuild" data-kind="project" data-shape="rounded" data-tone="1" data-icon="ri-rocket-2-line"
data-root data-weight="16" data-meta="Project · due in Q4">Checkout rebuild</li>
<li data-node="alex" data-kind="person" data-tone="2" data-icon="ri-user-3-line" data-meta="Engineering lead">Alex Fischer</li>
<li data-node="priya" data-kind="person" data-tone="2" data-icon="ri-user-3-line" data-meta="Payments engineer">Priya Nair</li>
<li data-node="jordan" data-kind="person" data-tone="2" data-icon="ri-user-3-line" data-meta="Product manager">Jordan Weiss</li>
<li data-node="rin" data-kind="person" data-tone="2" data-icon="ri-user-3-line" data-meta="Designer">Rin Nakamura</li>
<li data-node="web-team" data-kind="team" data-shape="hexagon" data-tone="3" data-icon="ri-team-line" data-hub data-meta="Team · 6 people">Web team</li>
<li data-node="payments-team" data-kind="team" data-shape="hexagon" data-tone="3" data-icon="ri-team-line" data-hub data-meta="Team · 4 people">Payments team</li>
<li data-node="brief" data-kind="document" data-shape="square" data-tone="4" data-icon="ri-file-text-line" data-meta="Document">Design brief</li>
<li data-node="runbook" data-kind="document" data-shape="square" data-tone="4" data-icon="ri-file-text-line" data-meta="Document">Rollout runbook</li>
<li data-node="load-test" data-kind="document" data-shape="square" data-tone="4" data-icon="ri-file-chart-line" data-meta="Document">Load test report</li>
<li data-node="beta" data-kind="milestone" data-shape="star" data-tone="5" data-meta="Milestone · 14 October">Staff beta</li>
<li data-node="release" data-kind="milestone" data-shape="star" data-tone="5" data-meta="Milestone · 18 November">General release</li>
<li data-node="pci" data-kind="tag" data-shape="tag" data-tone="6" data-meta="Tag">pci</li>
<li data-node="q4" data-kind="tag" data-shape="tag" data-tone="6" data-meta="Tag">q4</li>
<li data-node="one-page" data-kind="decision" data-shape="diamond" data-tone="go" data-meta="Decision · accepted">One checkout page</li>
<li data-node="tokenise" data-kind="decision" data-shape="diamond" data-tone="go" data-meta="Decision · accepted">Tokenise cards at the edge</li>
<li data-node="three-step" data-kind="decision" data-shape="diamond" data-tone="go" data-muted data-meta="Decision · superseded">Three-step checkout</li>
<li data-node="cut-over" data-kind="risk" data-shape="triangle" data-tone="danger" data-meta="Risk · blocking">Card provider cut-over</li>
<li data-node="freeze" data-kind="risk" data-shape="triangle" data-tone="warn" data-meta="Risk · open">Holiday change freeze</li>
<li data-edge data-source="alex" data-target="rebuild" data-label="leads">Alex Fischer leads the checkout rebuild</li>
<li data-edge data-source="jordan" data-target="rebuild" data-label="owns">Jordan Weiss owns the checkout rebuild</li>
<li data-edge data-source="web-team" data-target="rebuild" data-label="builds">The web team builds the checkout rebuild</li>
<li data-edge data-source="payments-team" data-target="rebuild" data-label="builds">The payments team builds the checkout rebuild</li>
<li data-edge data-source="alex" data-target="web-team" data-label="member of">Alex Fischer is in the web team</li>
<li data-edge data-source="rin" data-target="web-team" data-label="member of">Rin Nakamura is in the web team</li>
<li data-edge data-source="priya" data-target="payments-team" data-label="member of">Priya Nair is in the payments team</li>
<li data-edge data-source="rin" data-target="brief" data-label="wrote">Rin Nakamura wrote the design brief</li>
<li data-edge data-source="brief" data-target="rebuild" data-label="describes">The design brief describes the checkout rebuild</li>
<li data-edge data-source="alex" data-target="runbook" data-label="wrote">Alex Fischer wrote the rollout runbook</li>
<li data-edge data-source="runbook" data-target="release" data-label="prepares">The rollout runbook prepares the general release</li>
<li data-edge data-source="load-test" data-target="beta" data-label="gates">The load test report gates the staff beta</li>
<li data-edge data-source="priya" data-target="load-test" data-label="ran">Priya Nair ran the load test</li>
<li data-edge data-source="rebuild" data-target="beta" data-label="next">The staff beta is the next milestone</li>
<li data-edge data-source="beta" data-target="release" data-label="then">The general release follows the staff beta</li>
<li data-edge data-source="jordan" data-target="one-page" data-label="proposed">Jordan Weiss proposed one checkout page</li>
<li data-edge data-source="one-page" data-target="rebuild" data-label="decided for">One checkout page was decided for the rebuild</li>
<li data-edge data-source="one-page" data-target="three-step" data-label="supersedes" data-line="dashed">One checkout page supersedes the three-step checkout</li>
<li data-edge data-source="priya" data-target="tokenise" data-label="proposed">Priya Nair proposed tokenising cards at the edge</li>
<li data-edge data-source="tokenise" data-target="rebuild" data-label="decided for">Tokenising cards at the edge was decided for the rebuild</li>
<li data-edge data-source="tokenise" data-target="cut-over" data-label="mitigates">Tokenising cards at the edge mitigates the card provider cut-over</li>
<li data-edge data-source="cut-over" data-target="release" data-label="blocks" data-tone="danger">The card provider cut-over blocks the general release</li>
<li data-edge data-source="freeze" data-target="release" data-label="threatens" data-tone="warn">The holiday change freeze threatens the general release</li>
<li data-edge data-source="rebuild" data-target="pci" data-label="tagged">The checkout rebuild is tagged pci</li>
<li data-edge data-source="tokenise" data-target="pci" data-label="tagged">Tokenising cards at the edge is tagged pci</li>
<li data-edge data-source="beta" data-target="q4" data-label="tagged">The staff beta is tagged q4</li>
<li data-edge data-source="release" data-target="q4" data-label="tagged">The general release is tagged q4</li>
</ul>
<div class="graph-canvas"></div>
<div class="graph-wait skeleton skeleton-block"></div>
</div>
<div class="graph-legend">
<span class="graph-key"><span class="graph-swatch graph-swatch--rounded series-1"></span><span>Project</span></span>
<span class="graph-key"><span class="graph-swatch series-2"></span><span>Person</span></span>
<span class="graph-key"><span class="graph-swatch graph-swatch--hexagon series-3"></span><span>Team</span></span>
<span class="graph-key"><span class="graph-swatch graph-swatch--square series-4"></span><span>Document</span></span>
<span class="graph-key"><span class="graph-swatch graph-swatch--star series-5"></span><span>Milestone</span></span>
<span class="graph-key"><span class="graph-swatch graph-swatch--tag series-6"></span><span>Tag</span></span>
</div>
<div class="graph-legend">
<span class="graph-key"><span class="graph-swatch graph-swatch--diamond series-go"></span><span>Decision</span></span>
<span class="graph-key"><span class="graph-swatch graph-swatch--diamond graph-swatch--muted series-go"></span><span>Superseded</span></span>
<span class="graph-key"><span class="graph-swatch graph-swatch--triangle series-warn"></span><span>Open risk</span></span>
<span class="graph-key"><span class="graph-swatch graph-swatch--triangle series-danger"></span><span>Blocking risk</span></span>
</div>
</div>
Every shape
The shapes data-shape takes. The diamond, hexagon, octagon, pentagon, triangle and tag are
drawn with rounded corners, because a hard corner reads as a different shape at the size of a dot. A legend has a swatch for the circle, square,
rounded, diamond, triangle, hexagon, star and tag, so give the kinds a reader must tell apart one of
those. data-graph-labels="all" names every record whatever the room, which suits a key like
this one and crowds a map; none names only the record being pointed at.
- circle
- square
- rounded
- diamond
- hexagon
- octagon
- pentagon
- triangle
- tag
- star
- barrel
- rhomboid
- vee
<div class="graph graph--sm" data-graph id="graph-shapes" data-graph-layout="grid" data-graph-labels="all" data-graph-spacing="loose"
aria-label="Every shape a record can take">
<ul class="graph-data" data-graph-data>
<li data-node="circle" data-weight="9" data-tone="1" data-shape="circle">circle</li>
<li data-node="square" data-weight="9" data-tone="2" data-shape="square">square</li>
<li data-node="rounded" data-weight="9" data-tone="3" data-shape="rounded">rounded</li>
<li data-node="diamond" data-weight="9" data-tone="4" data-shape="diamond">diamond</li>
<li data-node="hexagon" data-weight="9" data-tone="5" data-shape="hexagon">hexagon</li>
<li data-node="octagon" data-weight="9" data-tone="6" data-shape="octagon">octagon</li>
<li data-node="pentagon" data-weight="9" data-tone="1" data-shape="pentagon">pentagon</li>
<li data-node="triangle" data-weight="9" data-tone="2" data-shape="triangle">triangle</li>
<li data-node="tag" data-weight="9" data-tone="3" data-shape="tag">tag</li>
<li data-node="star" data-weight="9" data-tone="4" data-shape="star">star</li>
<li data-node="barrel" data-weight="9" data-tone="5" data-shape="barrel">barrel</li>
<li data-node="rhomboid" data-weight="9" data-tone="6" data-shape="rhomboid">rhomboid</li>
<li data-node="vee" data-weight="9" data-tone="1" data-shape="vee">vee</li>
</ul>
<div class="graph-canvas"></div>
<div class="graph-wait skeleton skeleton-block"></div>
</div>
Tones that mean something
A health map, where the colour is the point. The semantic tones are a judgement the reader already
makes — green is fine, red is down — so use them only where a record is in that state, and
never to tell categories apart: a red team says “danger” whatever the data says. A link
takes a data-tone too, so the failing call is as red as the provider it cannot reach.
brand and accent are the two tones of the app's own identity.
The retired service is data-muted with no tone of its own, which draws it in the quiet
muted tone; its key is .series-muted with .graph-swatch--muted.
The outside provider's key is a shape with no colour, because being outside is a kind and not a state.
- Gateway
- Storefront
- Search
- Recommendations
- Checkout
- Payments
- Card provider
- Orders
- Stock
- Order mails
- Legacy cart
- The gateway routes to the storefront
- The storefront calls search
- The storefront calls recommendations
- The storefront hands over to checkout
- Checkout waits on payments
- Payments cannot reach the card provider
- Checkout places the order
- Orders reserves stock
- Orders sends the confirmation
- The gateway still has a route to the legacy cart
<div class="graph-frame">
<div class="graph" data-graph id="graph-health" data-graph-arrows="target" aria-label="Service health, now">
<ul class="graph-data" data-graph-data>
<li data-node="gateway" data-tone="go" data-icon="ri-door-open-line" data-meta="Healthy · 38 ms">Gateway</li>
<li data-node="storefront" data-tone="go" data-icon="ri-store-2-line" data-meta="Healthy · 112 ms">Storefront</li>
<li data-node="search" data-tone="go" data-icon="ri-search-line" data-meta="Healthy · 64 ms">Search</li>
<li data-node="recommend" data-tone="info" data-icon="ri-sparkling-line" data-meta="Deploying v4.2.0 · 3 of 5 pods">Recommendations</li>
<li data-node="checkout" data-tone="warn" data-icon="ri-secure-payment-line" data-meta="Slow · 2.4 s">Checkout</li>
<li data-node="payments" data-tone="warn" data-icon="ri-bank-card-line" data-meta="Slow · 2.1 s">Payments</li>
<li data-node="card-provider" data-tone="danger" data-shape="hexagon" data-icon="ri-global-line"
data-meta="Down since 09:42 · outside provider">Card provider</li>
<li data-node="orders" data-tone="go" data-icon="ri-file-list-3-line" data-meta="Healthy · 71 ms">Orders</li>
<li data-node="stock" data-tone="go" data-icon="ri-stack-line" data-meta="Healthy · 45 ms">Stock</li>
<li data-node="mail" data-tone="go" data-icon="ri-mail-send-line" data-meta="Healthy · queue empty">Order mails</li>
<li data-node="legacy-cart" data-muted data-icon="ri-shopping-cart-2-line" data-meta="Retired in March">Legacy cart</li>
<li data-edge data-source="gateway" data-target="storefront" data-label="routes">The gateway routes to the storefront</li>
<li data-edge data-source="storefront" data-target="search" data-label="calls">The storefront calls search</li>
<li data-edge data-source="storefront" data-target="recommend" data-label="calls">The storefront calls recommendations</li>
<li data-edge data-source="storefront" data-target="checkout" data-label="hands over">The storefront hands over to checkout</li>
<li data-edge data-source="checkout" data-target="payments" data-tone="warn" data-label="waits on">Checkout waits on payments</li>
<li data-edge data-source="payments" data-target="card-provider" data-tone="danger" data-line="dashed" data-label="cannot reach">Payments cannot reach the card provider</li>
<li data-edge data-source="checkout" data-target="orders" data-label="places">Checkout places the order</li>
<li data-edge data-source="orders" data-target="stock" data-label="reserves">Orders reserves stock</li>
<li data-edge data-source="orders" data-target="mail" data-label="confirms">Orders sends the confirmation</li>
<li data-edge data-source="gateway" data-target="legacy-cart" data-muted data-label="old route">The gateway still has a route to the legacy cart</li>
</ul>
<div class="graph-canvas"></div>
<div class="graph-wait skeleton skeleton-block"></div>
</div>
<div class="graph-legend">
<span class="graph-key"><span class="graph-swatch series-go"></span><span>Healthy</span></span>
<span class="graph-key"><span class="graph-swatch series-info"></span><span>Deploying</span></span>
<span class="graph-key"><span class="graph-swatch series-warn"></span><span>Slow</span></span>
<span class="graph-key"><span class="graph-swatch series-danger"></span><span>Down</span></span>
<span class="graph-key"><span class="graph-swatch graph-swatch--muted series-muted"></span><span>Retired</span></span>
<span class="graph-key"><span class="graph-swatch graph-swatch--hexagon"></span><span>Outside provider</span></span>
</div>
</div>
Links
A link's line says how its two ends are joined. data-line is solid,
dashed or dotted; data-weight is light,
normal, heavy or a number; data-arrow is target,
source, both or none, over the graph's own
data-graph-arrows. data-tone colours a link and data-muted
quietens it. In a graph of dots a link's label shows when one of its ends is pointed at;
data-graph-edge-labels="always" writes every one, for a small graph whose links are the
story, and none writes none.
Each .graph-line in the legend takes the same modifiers — --dashed,
--dotted, --light, --heavy, and --arrow for a link
with a direction — and a .series-* class for a tone. Give every kind of line a key: a
dashed line nobody explained is a guess. --flow is the key for a link a run is sending data
along; the gallery's automation flow shows a run.
- Checkout
- Orders API
- Fraud check
- src-db-14
- src-db-15
- Search index
- Nightly loader
- Data warehouse
- Reports
- Audit log
- Checkout sends every order to the orders API
- The orders API asks the fraud check about each order and waits for the answer
- The orders API writes each order to src-db-14
- src-db-14 replicates to src-db-15, forty seconds behind
- The orders API indexes each order for search, on a best-effort basis
- The nightly loader reads src-db-15 at two in the morning
- The nightly loader loads the data warehouse
- The data warehouse feeds the reports
- The orders API records each change in the audit log
- Reports read src-db-15 directly, which is to be removed
<div class="graph-frame">
<div class="graph" data-graph id="graph-data-flow" data-graph-layout="dagre" data-graph-direction="LR"
data-graph-edge-labels="always" data-graph-arrows="target" data-graph-spacing="loose"
aria-label="Where order data goes">
<ul class="graph-data" data-graph-data>
<li data-node="checkout" data-tone="1" data-icon="ri-secure-payment-line">Checkout</li>
<li data-node="orders-api" data-tone="2" data-icon="ri-server-line">Orders API</li>
<li data-node="fraud" data-tone="3" data-icon="ri-shield-check-line">Fraud check</li>
<li data-node="orders-db" data-tone="4" data-shape="barrel" data-icon="ri-database-2-line">src-db-14</li>
<li data-node="replica" data-tone="4" data-shape="barrel" data-icon="ri-database-2-line">src-db-15</li>
<li data-node="search" data-tone="5" data-icon="ri-search-line">Search index</li>
<li data-node="loader" data-tone="6" data-icon="ri-timer-line">Nightly loader</li>
<li data-node="warehouse" data-tone="6" data-shape="barrel" data-icon="ri-database-line">Data warehouse</li>
<li data-node="reports" data-tone="6" data-icon="ri-bar-chart-box-line">Reports</li>
<li data-node="audit" data-tone="muted" data-icon="ri-file-shield-2-line">Audit log</li>
<li data-edge data-source="checkout" data-target="orders-api" data-label="orders" data-weight="heavy">Checkout sends every order to the orders API</li>
<li data-edge data-source="orders-api" data-target="fraud" data-label="asks, is answered" data-arrow="both">The orders API asks the fraud check about each order and waits for the answer</li>
<li data-edge data-source="orders-api" data-target="orders-db" data-label="writes" data-weight="heavy">The orders API writes each order to src-db-14</li>
<li data-edge data-source="orders-db" data-target="replica" data-label="replicates · 40 s behind" data-tone="warn">src-db-14 replicates to src-db-15, forty seconds behind</li>
<li data-edge data-source="orders-api" data-target="search" data-label="indexes, best effort" data-line="dotted" data-weight="light">The orders API indexes each order for search, on a best-effort basis</li>
<li data-edge data-source="replica" data-target="loader" data-label="read at 02:00" data-line="dashed">The nightly loader reads src-db-15 at two in the morning</li>
<li data-edge data-source="loader" data-target="warehouse" data-label="loads" data-line="dashed">The nightly loader loads the data warehouse</li>
<li data-edge data-source="warehouse" data-target="reports" data-label="feeds">The data warehouse feeds the reports</li>
<li data-edge data-source="orders-api" data-target="audit" data-label="records" data-weight="light" data-muted>The orders API records each change in the audit log</li>
<li data-edge data-source="replica" data-target="reports" data-label="read directly · to remove" data-tone="danger">Reports read src-db-15 directly, which is to be removed</li>
</ul>
<div class="graph-canvas"></div>
<div class="graph-wait skeleton skeleton-block"></div>
</div>
<div class="graph-legend">
<span class="graph-key"><span class="graph-line graph-line--heavy graph-line--arrow"></span><span>The hot path</span></span>
<span class="graph-key"><span class="graph-line graph-line--arrow"></span><span>Every change, as it happens</span></span>
<span class="graph-key"><span class="graph-line graph-line--dashed graph-line--arrow"></span><span>On a schedule</span></span>
<span class="graph-key"><span class="graph-line graph-line--dotted graph-line--light graph-line--arrow"></span><span>Best effort</span></span>
<span class="graph-key"><span class="graph-line graph-line--arrow series-warn"></span><span>Lagging</span></span>
<span class="graph-key"><span class="graph-line graph-line--arrow series-danger"></span><span>To be removed</span></span>
</div>
</div>
Dots and boxes
Live — switch between the two. Dots are for a map of many records, where the
shape of the whole is the point: each record a shape sized by its links, with its name beneath. Boxes,
data-graph-nodes="box", are for a diagram of a few dozen that is read one record at a
time — a request path, an org chart, a flow — with the name inside, the icon before it and
data-meta as a second line. A graph of boxes writes its links' labels by default.
data-display on one record draws it the other way from the rest.
The switch is an ordinary radio group with data-graph-option="nodes". Inside a
[data-graph-frame] it finds its graph without an id, and a radio keeps its native state, so
a Blazor binding can own it.
- Edge cache
- Load balancer
- Storefront
- Session cache
- Orders API
- order-events
- src-db-14
- src-db-15
- The edge cache asks the load balancer when it has no copy
- The load balancer spreads requests over the storefront
- The storefront keeps sessions in the session cache
- The storefront calls the orders API
- The orders API writes to src-db-14
- The orders API publishes to order-events
- src-db-14 replicates to src-db-15
<div class="graph-frame" data-graph-frame>
<div class="toolbar">
<div class="segmented segmented--sm" role="group" aria-label="Draw the records as">
<label class="segmented-option">
<input type="radio" name="graph-request-drawn" value="box" data-graph-option="nodes" checked />
<i class="ri-layout-row-line"></i> <span>Boxes</span>
</label>
<label class="segmented-option">
<input type="radio" name="graph-request-drawn" value="dot" data-graph-option="nodes" />
<i class="ri-bubble-chart-line"></i> <span>Dots</span>
</label>
</div>
</div>
<div class="card">
<div class="graph graph--sm" data-graph id="graph-request-path" data-graph-nodes="box" data-graph-layout="dagre" data-graph-direction="LR"
data-graph-arrows="target" aria-label="The path of one request, from the edge to the database">
<ul class="graph-data" data-graph-data>
<li data-node="edge" data-tone="1" data-icon="ri-flashlight-line" data-meta="203.0.113.10">Edge cache</li>
<li data-node="lb" data-tone="2" data-icon="ri-git-branch-line" data-meta="lb-01">Load balancer</li>
<li data-node="web" data-tone="3" data-icon="ri-store-2-line" data-meta="3 hosts · v2.14.1">Storefront</li>
<li data-node="sessions" data-tone="3" data-icon="ri-key-2-line" data-meta="1.2 GB">Session cache</li>
<li data-node="api" data-tone="4" data-icon="ri-server-line" data-meta="2 hosts · v5.3.0">Orders API</li>
<li data-node="events" data-tone="5" data-icon="ri-broadcast-line" data-meta="12 subscribers">order-events</li>
<li data-node="db" data-tone="6" data-icon="ri-database-2-line" data-meta="Primary">src-db-14</li>
<li data-node="replica" data-tone="6" data-icon="ri-database-2-line" data-meta="40 s behind">src-db-15</li>
<li data-edge data-source="edge" data-target="lb" data-label="on a miss">The edge cache asks the load balancer when it has no copy</li>
<li data-edge data-source="lb" data-target="web" data-label="round robin">The load balancer spreads requests over the storefront</li>
<li data-edge data-source="web" data-target="sessions" data-label="sessions">The storefront keeps sessions in the session cache</li>
<li data-edge data-source="web" data-target="api" data-label="REST">The storefront calls the orders API</li>
<li data-edge data-source="api" data-target="db" data-label="writes">The orders API writes to src-db-14</li>
<li data-edge data-source="api" data-target="events" data-label="publishes">The orders API publishes to order-events</li>
<li data-edge data-source="db" data-target="replica" data-label="replicates" data-line="dashed">src-db-14 replicates to src-db-15</li>
</ul>
<div class="graph-canvas"></div>
<div class="graph-wait skeleton skeleton-block"></div>
</div>
</div>
</div>
A small graph in a card
.graph--sm is for a graph that is part of a page about something else: what one record
touches, in a card on its detail page, with data-root putting that record at the centre.
.graph--lg is the tall one, for a graph that is the subject of its page;
.graph--fill takes its container's height, so give the container one; and
.graph--viewport takes what the viewport has left under whatever is above it, for an app's
own graph page. Any other height is --graph-height: on the graph, or on its
.graph-frame to size a side panel beside it to match.
The count in the card's head is a data-graph-stats outside the graph, which names it with
data-graph-for.
Linked to src-db-14
- src-db-14
- src-db-15
- Orders API
- Invoices
- Returns
- Backup
- Warehouse loader
- build-runner-04
- Data team
- The orders API reads and writes src-db-14
- Invoices reads and writes src-db-14
- Returns reads src-db-14
- src-db-14 replicates to src-db-15
- The backup job copies src-db-14 every night
- The warehouse loader reads src-db-15 every night
- build-runner-04 runs the migrations on src-db-14
- The data team owns src-db-14
- The data team owns src-db-15
<div class="card" style="max-width:560px">
<div class="card-head">
<h3>Linked to src-db-14</h3>
<div class="card-head-right">
<span class="toolbar-count" data-graph-for="graph-db-neighbours" data-graph-stats="{nodes} records"></span>
</div>
</div>
<div class="card-body card-body--flush">
<div class="graph graph--sm" data-graph id="graph-db-neighbours" aria-label="What is linked to src-db-14">
<ul class="graph-data" data-graph-data>
<li data-node="db" data-root data-tone="4" data-icon="ri-database-2-line" data-meta="Primary database">src-db-14</li>
<li data-node="replica" data-tone="4" data-icon="ri-database-2-line" data-meta="Replica">src-db-15</li>
<li data-node="orders" data-tone="2" data-icon="ri-server-line" data-meta="Service">Orders API</li>
<li data-node="invoices" data-tone="2" data-icon="ri-bill-line" data-meta="Service">Invoices</li>
<li data-node="returns" data-tone="2" data-icon="ri-arrow-go-back-line" data-meta="Service">Returns</li>
<li data-node="backup" data-tone="5" data-icon="ri-save-3-line" data-meta="Nightly job">Backup</li>
<li data-node="loader" data-tone="5" data-icon="ri-timer-line" data-meta="Nightly job">Warehouse loader</li>
<li data-node="runner" data-tone="muted" data-icon="ri-terminal-box-line" data-meta="Migrations">build-runner-04</li>
<li data-node="dba" data-tone="3" data-icon="ri-team-line" data-meta="Owner">Data team</li>
<li data-edge data-source="orders" data-target="db" data-label="reads, writes">The orders API reads and writes src-db-14</li>
<li data-edge data-source="invoices" data-target="db" data-label="reads, writes">Invoices reads and writes src-db-14</li>
<li data-edge data-source="returns" data-target="db" data-label="reads">Returns reads src-db-14</li>
<li data-edge data-source="db" data-target="replica" data-label="replicates">src-db-14 replicates to src-db-15</li>
<li data-edge data-source="backup" data-target="db" data-label="nightly copy">The backup job copies src-db-14 every night</li>
<li data-edge data-source="loader" data-target="replica" data-label="nightly read">The warehouse loader reads src-db-15 every night</li>
<li data-edge data-source="runner" data-target="db" data-label="migrates">build-runner-04 runs the migrations on src-db-14</li>
<li data-edge data-source="dba" data-target="db" data-label="owns">The data team owns src-db-14</li>
<li data-edge data-source="dba" data-target="replica" data-label="owns">The data team owns src-db-15</li>
</ul>
<div class="graph-canvas"></div>
<div class="graph-wait skeleton skeleton-block"></div>
</div>
</div>
</div>
A graph that fills its pane
.graph--fill is as tall as its container, for a pane of a dashboard whose height is set by
the layout rather than by the graph. Here the container is the body of a .card.card--fill
with a height, so the graph takes what the head leaves.
data-graph-colour names the colouring the graph starts in: tone, or the name
after a record's data-tone- — today here, so the legend is the day's state
and the kinds are left to the shapes. [data-graph-canvas] names the element the engine draws
into, in place of .graph-canvas, for an app whose canvas element carries a class of its own;
that element has to take the graph's one grid cell, as the inline style does here.
Deliveries today
- North hub
- Hilltop depot
- Harbour depot
- Airport depot
- Old Town store
- Seafront store
- Stadium store
- North hub supplies Hilltop depot
- North hub supplies Harbour depot
- North hub supplies Airport depot
- Hilltop depot delivers to Old Town store
- Harbour depot delivers to Seafront store
- Airport depot delivers to Stadium store
<div class="card card--fill" style="height:22rem; max-width:640px">
<div class="card-head">
<h3>Deliveries today</h3>
<div class="graph-legend">
<span class="graph-key"><span class="graph-swatch series-go"></span> <span>On time</span></span>
<span class="graph-key"><span class="graph-swatch series-warn"></span> <span>Late</span></span>
<span class="graph-key"><span class="graph-swatch series-danger"></span> <span>Stopped</span></span>
</div>
</div>
<div class="card-body card-body--flush">
<div class="graph graph--fill" data-graph id="graph-fill-pane" data-graph-colour="today" data-graph-arrows="target"
aria-label="Today's deliveries from the hub through the depots">
<ul class="graph-data" data-graph-data>
<li data-node="hub" data-tone="3" data-tone-today="go" data-shape="star" data-icon="ri-building-4-line" data-meta="Hub · on time" data-root>North hub</li>
<li data-node="hilltop" data-tone="5" data-tone-today="go" data-shape="rounded" data-meta="Depot · on time">Hilltop depot</li>
<li data-node="harbour" data-tone="5" data-tone-today="warn" data-shape="rounded" data-meta="Depot · 40 minutes late">Harbour depot</li>
<li data-node="airport" data-tone="5" data-tone-today="danger" data-shape="rounded" data-meta="Depot · stopped at 11:20">Airport depot</li>
<li data-node="old-town" data-tone="2" data-tone-today="go" data-meta="Store · on time">Old Town store</li>
<li data-node="seafront" data-tone="2" data-tone-today="warn" data-meta="Store · late">Seafront store</li>
<li data-node="stadium" data-tone="2" data-tone-today="danger" data-meta="Store · waiting">Stadium store</li>
<li data-edge data-source="hub" data-target="hilltop" data-label="supplies">North hub supplies Hilltop depot</li>
<li data-edge data-source="hub" data-target="harbour" data-label="supplies">North hub supplies Harbour depot</li>
<li data-edge data-source="hub" data-target="airport" data-label="supplies">North hub supplies Airport depot</li>
<li data-edge data-source="hilltop" data-target="old-town" data-label="delivers">Hilltop depot delivers to Old Town store</li>
<li data-edge data-source="harbour" data-target="seafront" data-label="delivers">Harbour depot delivers to Seafront store</li>
<li data-edge data-source="airport" data-target="stadium" data-label="delivers">Airport depot delivers to Stadium store</li>
</ul>
<!-- The app's own element for the canvas, named by data-graph-canvas. The style stands in
for the app's own rule: it takes the graph's one grid cell, as .graph-canvas does. -->
<div data-graph-canvas style="grid-area: 1 / 1 / -1 / -1; position: relative; z-index: 0; min-width: 0; min-height: 0"></div>
<div class="graph-wait skeleton skeleton-block"></div>
</div>
</div>
</div>
A few records on a wall screen
A fit stops a little past full size, so a handful of records in a big frame stay small and close
together. On a screen read from across a room, data-graph-fit-max lets the fit zoom
further and spread them over the frame, and data-graph-scale draws the same drawing
bigger — names, records and lines together, 1.5 times here, the fit's ceiling of
2 with them.
An app that scales its whole frame with CSS zoom for a big screen gets a sharp canvas:
the graph draws at the screen's pixel ratio times that zoom. A tap or a click focuses the graph
without its ring, which comes back at the first key.
- Incoming call
- Alex Fischer
- Printer offline
- New ticket
- Priority 3
- The call is from Alex Fischer
- The call is about an offline printer
- The problem is filed as a new ticket
- The ticket is set to priority 3
<div class="graph graph--lg" data-graph id="graph-wall" data-graph-fit-max="2" data-graph-scale="1.5" data-graph-wheel="none" aria-label="What the call has noted so far">
<ul class="graph-data" data-graph-data>
<li data-node="call" data-root data-tone="2" data-icon="ri-phone-line" data-meta="Call">Incoming call</li>
<li data-node="caller" data-tone="3" data-icon="ri-user-line" data-meta="Person">Alex Fischer</li>
<li data-node="problem" data-tone="5" data-icon="ri-error-warning-line" data-meta="Problem">Printer offline</li>
<li data-node="ticket" data-tone="4" data-icon="ri-ticket-line" data-meta="Ticket">New ticket</li>
<li data-node="priority" data-tone="1" data-icon="ri-flag-line" data-meta="Decision">Priority 3</li>
<li data-edge data-source="call" data-target="caller" data-label="from">The call is from Alex Fischer</li>
<li data-edge data-source="call" data-target="problem" data-label="about">The call is about an offline printer</li>
<li data-edge data-source="problem" data-target="ticket" data-label="filed as">The problem is filed as a new ticket</li>
<li data-edge data-source="ticket" data-target="priority" data-label="set to">The ticket is set to priority 3</li>
</ul>
<div class="graph-canvas"></div>
<div class="graph-wait skeleton skeleton-block"></div>
</div>
A large graph
A .graph--lg network, with its tools where a map keeps them.
.graph-tools--start puts a toolbar at the top left, --stack turns it into a
column, and --bottom sends a second one to the bottom edge. .graph-minimap--start
moves the minimap to the bottom left and .graph-minimap--top to the top; put it where the
drawing is emptiest. With no .graph-frame around it, full screen takes the graph alone.
Every link here is a cable, so a label would add nothing and data-graph-edge-labels="none"
leaves them off. The wheel zooms only once the canvas has been pressed, so a graph in a page that
scrolls does not swallow the scroll; data-graph-wheel="always" is for a graph that is the
whole page.
- Internet
- fw-01
- core-01
- core-02
- dist-01
- dist-02
- dist-03
- dist-04
- access-01
- access-02
- access-03
- access-04
- access-05
- access-06
- access-07
- access-08
- orders-console-01
- orders-console-02
- build-runner-01
- build-runner-02
- build-runner-03
- build-runner-04
- src-db-14
- src-db-15
- web-01
- web-02
- web-03
- api-01
- api-02
- cache-01
- mq-01
- backup-01
- log-01
- scanner-hub-01
- label-printer-02
- The internet uplink enters through fw-01
- fw-01 connects to core-01
- fw-01 connects to core-02
- core-01 and core-02 are linked to each other
- core-01 connects to dist-01
- core-02 is the standby path to dist-01
- core-01 connects to dist-02
- core-02 is the standby path to dist-02
- core-02 connects to dist-03
- core-01 is the standby path to dist-03
- core-02 connects to dist-04
- core-01 is the standby path to dist-04
- dist-01 connects to access-01
- dist-01 connects to access-02
- dist-02 connects to access-03
- dist-02 connects to access-04
- dist-03 connects to access-05
- dist-03 connects to access-06
- dist-04 connects to access-07
- dist-04 connects to access-08
- orders-console-01 is on access-01
- orders-console-02 is on access-01
- build-runner-01 is on access-02
- build-runner-02 is on access-02
- build-runner-03 is on access-02
- build-runner-04 is on access-02
- src-db-14 is on access-03
- src-db-15 is on access-03
- web-01 is on access-04
- web-02 is on access-04
- web-03 is on access-04
- api-01 is on access-05
- api-02 is on access-05
- cache-01 is on access-06
- mq-01 is on access-06
- backup-01 is on access-07
- log-01 is on access-07
- scanner-hub-01 is on access-08
- label-printer-02 is on access-08
<div class="card">
<div class="graph graph--lg" data-graph id="graph-network" data-graph-edge-labels="none" aria-label="The office network, switch by switch">
<ul class="graph-data" data-graph-data>
<li data-node="internet" data-shape="hexagon" data-tone="muted" data-icon="ri-global-line" data-meta="Uplink · 203.0.113.1">Internet</li>
<li data-node="fw-01" data-shape="octagon" data-tone="danger" data-icon="ri-shield-keyhole-line" data-meta="Firewall · 198.51.100.1">fw-01</li>
<li data-node="core-01" data-shape="square" data-tone="1" data-icon="ri-router-line" data-hub data-meta="Core switch">core-01</li>
<li data-node="core-02" data-shape="square" data-tone="1" data-icon="ri-router-line" data-hub data-meta="Core switch">core-02</li>
<li data-node="dist-01" data-shape="rounded" data-tone="2" data-meta="Distribution switch">dist-01</li>
<li data-node="dist-02" data-shape="rounded" data-tone="2" data-meta="Distribution switch">dist-02</li>
<li data-node="dist-03" data-shape="rounded" data-tone="2" data-meta="Distribution switch">dist-03</li>
<li data-node="dist-04" data-shape="rounded" data-tone="2" data-meta="Distribution switch">dist-04</li>
<li data-node="access-01" data-shape="rounded" data-tone="3" data-meta="Access switch · floor 1">access-01</li>
<li data-node="access-02" data-shape="rounded" data-tone="3" data-meta="Access switch · floor 1">access-02</li>
<li data-node="access-03" data-shape="rounded" data-tone="3" data-meta="Access switch · server room">access-03</li>
<li data-node="access-04" data-shape="rounded" data-tone="3" data-meta="Access switch · server room">access-04</li>
<li data-node="access-05" data-shape="rounded" data-tone="3" data-meta="Access switch · server room">access-05</li>
<li data-node="access-06" data-shape="rounded" data-tone="3" data-meta="Access switch · server room">access-06</li>
<li data-node="access-07" data-shape="rounded" data-tone="3" data-meta="Access switch · floor 2">access-07</li>
<li data-node="access-08" data-shape="rounded" data-tone="3" data-meta="Access switch · warehouse">access-08</li>
<li data-node="orders-console-01" data-tone="4" data-meta="198.51.100.31">orders-console-01</li>
<li data-node="orders-console-02" data-tone="4" data-meta="198.51.100.32">orders-console-02</li>
<li data-node="build-runner-01" data-tone="5" data-meta="198.51.100.41">build-runner-01</li>
<li data-node="build-runner-02" data-tone="5" data-meta="198.51.100.42">build-runner-02</li>
<li data-node="build-runner-03" data-tone="5" data-meta="198.51.100.43">build-runner-03</li>
<li data-node="build-runner-04" data-tone="5" data-meta="198.51.100.44">build-runner-04</li>
<li data-node="src-db-14" data-tone="6" data-meta="198.51.100.21">src-db-14</li>
<li data-node="src-db-15" data-tone="6" data-meta="198.51.100.22">src-db-15</li>
<li data-node="web-01" data-tone="4" data-meta="198.51.100.51">web-01</li>
<li data-node="web-02" data-tone="4" data-meta="198.51.100.52">web-02</li>
<li data-node="web-03" data-tone="4" data-meta="198.51.100.53">web-03</li>
<li data-node="api-01" data-tone="4" data-meta="198.51.100.61">api-01</li>
<li data-node="api-02" data-tone="4" data-meta="198.51.100.62">api-02</li>
<li data-node="cache-01" data-tone="6" data-meta="198.51.100.71">cache-01</li>
<li data-node="mq-01" data-tone="6" data-meta="198.51.100.72">mq-01</li>
<li data-node="backup-01" data-tone="6" data-meta="198.51.100.81">backup-01</li>
<li data-node="log-01" data-tone="6" data-meta="198.51.100.82">log-01</li>
<li data-node="scanner-hub-01" data-tone="4" data-meta="198.51.100.91">scanner-hub-01</li>
<li data-node="label-printer-02" data-tone="4" data-meta="198.51.100.92">label-printer-02</li>
<li data-edge data-source="internet" data-target="fw-01" data-weight="heavy">The internet uplink enters through fw-01</li>
<li data-edge data-source="fw-01" data-target="core-01" data-weight="heavy">fw-01 connects to core-01</li>
<li data-edge data-source="fw-01" data-target="core-02" data-weight="heavy">fw-01 connects to core-02</li>
<li data-edge data-source="core-01" data-target="core-02" data-weight="heavy">core-01 and core-02 are linked to each other</li>
<li data-edge data-source="core-01" data-target="dist-01">core-01 connects to dist-01</li>
<li data-edge data-source="core-02" data-target="dist-01" data-line="dashed">core-02 is the standby path to dist-01</li>
<li data-edge data-source="core-01" data-target="dist-02">core-01 connects to dist-02</li>
<li data-edge data-source="core-02" data-target="dist-02" data-line="dashed">core-02 is the standby path to dist-02</li>
<li data-edge data-source="core-02" data-target="dist-03">core-02 connects to dist-03</li>
<li data-edge data-source="core-01" data-target="dist-03" data-line="dashed">core-01 is the standby path to dist-03</li>
<li data-edge data-source="core-02" data-target="dist-04">core-02 connects to dist-04</li>
<li data-edge data-source="core-01" data-target="dist-04" data-line="dashed">core-01 is the standby path to dist-04</li>
<li data-edge data-source="dist-01" data-target="access-01">dist-01 connects to access-01</li>
<li data-edge data-source="dist-01" data-target="access-02">dist-01 connects to access-02</li>
<li data-edge data-source="dist-02" data-target="access-03">dist-02 connects to access-03</li>
<li data-edge data-source="dist-02" data-target="access-04">dist-02 connects to access-04</li>
<li data-edge data-source="dist-03" data-target="access-05">dist-03 connects to access-05</li>
<li data-edge data-source="dist-03" data-target="access-06">dist-03 connects to access-06</li>
<li data-edge data-source="dist-04" data-target="access-07">dist-04 connects to access-07</li>
<li data-edge data-source="dist-04" data-target="access-08">dist-04 connects to access-08</li>
<li data-edge data-source="access-01" data-target="orders-console-01">orders-console-01 is on access-01</li>
<li data-edge data-source="access-01" data-target="orders-console-02">orders-console-02 is on access-01</li>
<li data-edge data-source="access-02" data-target="build-runner-01">build-runner-01 is on access-02</li>
<li data-edge data-source="access-02" data-target="build-runner-02">build-runner-02 is on access-02</li>
<li data-edge data-source="access-02" data-target="build-runner-03">build-runner-03 is on access-02</li>
<li data-edge data-source="access-02" data-target="build-runner-04">build-runner-04 is on access-02</li>
<li data-edge data-source="access-03" data-target="src-db-14">src-db-14 is on access-03</li>
<li data-edge data-source="access-03" data-target="src-db-15">src-db-15 is on access-03</li>
<li data-edge data-source="access-04" data-target="web-01">web-01 is on access-04</li>
<li data-edge data-source="access-04" data-target="web-02">web-02 is on access-04</li>
<li data-edge data-source="access-04" data-target="web-03">web-03 is on access-04</li>
<li data-edge data-source="access-05" data-target="api-01">api-01 is on access-05</li>
<li data-edge data-source="access-05" data-target="api-02">api-02 is on access-05</li>
<li data-edge data-source="access-06" data-target="cache-01">cache-01 is on access-06</li>
<li data-edge data-source="access-06" data-target="mq-01">mq-01 is on access-06</li>
<li data-edge data-source="access-07" data-target="backup-01">backup-01 is on access-07</li>
<li data-edge data-source="access-07" data-target="log-01">log-01 is on access-07</li>
<li data-edge data-source="access-08" data-target="scanner-hub-01">scanner-hub-01 is on access-08</li>
<li data-edge data-source="access-08" data-target="label-printer-02">label-printer-02 is on access-08</li>
</ul>
<div class="graph-canvas"></div>
<div class="graph-tools graph-tools--start graph-tools--stack" role="toolbar" aria-label="Zoom">
<button class="btn btn--ghost btn--sm btn--icon" type="button" data-graph-action="zoom-in"
aria-label="Zoom in" data-tip="Zoom in"><i class="ri-add-line"></i></button>
<button class="btn btn--ghost btn--sm btn--icon" type="button" data-graph-action="zoom-out"
aria-label="Zoom out" data-tip="Zoom out"><i class="ri-subtract-line"></i></button>
<span class="graph-tools-sep" aria-hidden="true"></span>
<button class="btn btn--ghost btn--sm btn--icon" type="button" data-graph-action="fit"
aria-label="Fit" data-tip="Fit everything"><i class="ri-focus-3-line"></i></button>
</div>
<div class="graph-tools graph-tools--bottom" role="toolbar" aria-label="Drawing">
<button class="btn btn--ghost btn--sm" type="button" data-graph-action="arrange">
<i class="ri-magic-line"></i> <span>Arrange</span>
</button>
<button class="btn btn--ghost btn--sm btn--icon" type="button" data-graph-action="fullscreen"
aria-pressed="false" aria-label="Full screen" data-tip="Full screen">
<i class="ri-fullscreen-line graph-when-off"></i>
<i class="ri-fullscreen-exit-line graph-when-on"></i>
</button>
</div>
<canvas class="graph-minimap graph-minimap--start" data-graph-minimap aria-hidden="true"></canvas>
<div class="graph-wait skeleton skeleton-block"></div>
</div>
</div>
Waiting for records
data-graph-deferred says the records will arrive by a call — set() on the
handle sednaUi.graph.get() returns, or ISednaGraphs.SetDataAsync from C#
— so the .graph-wait stays up until they do, instead of an empty state flashing first.
Nothing calls this one, so it waits for good. Write the wait as a .skeleton: it is also what
shows before the script has started.
<div class="graph graph--sm" data-graph id="graph-waiting" data-graph-deferred
aria-label="Records linked to order 10482">
<div class="graph-canvas"></div>
<div class="graph-wait skeleton skeleton-block"></div>
<div class="graph-empty empty-state">
<svg class="state-art" aria-hidden="true"><use href="#nothing-yet" /></svg>
<span class="empty-state-title">Nothing is linked to this order yet</span>
</div>
</div>
Nothing to draw
An empty list draws nothing, and .graph-empty — an
empty state inside the graph — says why and what to do about it, in the
app's words. The script only decides when it shows.
Link a shipment, an invoice or a ticket, and it appears here with everything it touches.
<div class="graph graph--sm" data-graph id="graph-nothing-linked" aria-label="Records linked to order 10517">
<ul class="graph-data" data-graph-data></ul>
<div class="graph-canvas"></div>
<div class="graph-wait skeleton skeleton-block"></div>
<div class="graph-empty empty-state">
<svg class="state-art" aria-hidden="true"><use href="#nothing-yet" /></svg>
<span class="empty-state-title">Nothing is linked to this order yet</span>
<p>Link a shipment, an invoice or a ticket, and it appears here with everything it touches.</p>
<button class="btn btn--sm" type="button"><i class="ri-link"></i> <span>Link a record</span></button>
</div>
</div>
Filtered to nothing
Live — choose the data team. When there are records and the filters hide every
one, .graph-empty--filtered shows instead of the plain empty state: “nothing
matches” and “nothing exists” need different words and a different way out. Write
both, each on its own .graph-empty. The way out here is data-graph-action="reset",
which puts every control back as it was first drawn.
The select is a data-graph-filter naming a field — the records' own
data-team — and its empty value chooses them all. Chips, search and the rest of the
filters are on Exploring a graph.
- Storefront
- Checkout
- Payments
- Search
- Indexer
- Gateway
- Identity
- The gateway routes to the storefront
- The storefront calls search
- The storefront calls checkout
- Checkout calls payments
- Checkout calls identity
- The indexer feeds search
Every service on the map belongs to another team.
<div class="graph-frame" data-graph-frame>
<div class="toolbar">
<div class="toolbar-filters">
<select class="form-input form-select form-select--sm sedna-w-fit" aria-label="Owning team" data-graph-filter="team">
<option value="" selected>Every team</option>
<option value="checkout">Checkout team</option>
<option value="search">Search team</option>
<option value="platform">Platform team</option>
<option value="data">Data team</option>
</select>
</div>
<span class="toolbar-count" data-graph-stats="{nodes} of {totalNodes} services"></span>
</div>
<div class="card">
<div class="graph graph--sm" data-graph id="graph-by-team" data-graph-arrows="target"
aria-label="Services, by the team that owns them">
<ul class="graph-data" data-graph-data>
<li data-node="storefront" data-team="checkout" data-tone="1" data-icon="ri-store-2-line">Storefront</li>
<li data-node="checkout" data-team="checkout" data-tone="1" data-icon="ri-secure-payment-line">Checkout</li>
<li data-node="payments" data-team="checkout" data-tone="1" data-icon="ri-bank-card-line">Payments</li>
<li data-node="search" data-team="search" data-tone="2" data-icon="ri-search-line">Search</li>
<li data-node="indexer" data-team="search" data-tone="2" data-icon="ri-refresh-line">Indexer</li>
<li data-node="gateway" data-team="platform" data-tone="4" data-icon="ri-door-open-line">Gateway</li>
<li data-node="identity" data-team="platform" data-tone="4" data-icon="ri-fingerprint-line">Identity</li>
<li data-edge data-source="gateway" data-target="storefront" data-label="routes">The gateway routes to the storefront</li>
<li data-edge data-source="storefront" data-target="search" data-label="calls">The storefront calls search</li>
<li data-edge data-source="storefront" data-target="checkout" data-label="calls">The storefront calls checkout</li>
<li data-edge data-source="checkout" data-target="payments" data-label="calls">Checkout calls payments</li>
<li data-edge data-source="checkout" data-target="identity" data-label="calls">Checkout calls identity</li>
<li data-edge data-source="indexer" data-target="search" data-label="feeds">The indexer feeds search</li>
</ul>
<div class="graph-canvas"></div>
<div class="graph-wait skeleton skeleton-block"></div>
<div class="graph-empty empty-state">
<svg class="state-art" aria-hidden="true"><use href="#nothing-yet" /></svg>
<span class="empty-state-title">No services registered</span>
</div>
<div class="graph-empty graph-empty--filtered empty-state">
<svg class="state-art" aria-hidden="true"><use href="#filtered-out" /></svg>
<span class="empty-state-title">This team owns no services yet</span>
<p>Every service on the map belongs to another team.</p>
<button class="btn btn--sm" type="button" data-graph-action="reset">
<i class="ri-filter-off-line"></i> <span>Show every team</span>
</button>
</div>
</div>
</div>
</div>
When the records cannot be read
.graph-empty--error shows when the data could not be read: a data-graph-src
that answered with an error, or a response that is not JSON. Put it on an
.empty-state--failed that names the request, as any failed load would, and make its button
data-graph-action="reload", which fetches data-graph-src again. This one is held in
its error state for the page, which is why data-graph-state is written on it and
data-graph is not: in an app that attribute is the script's alone.
The request did not complete. Nothing has been changed.
GET /api/orders/10482/links · 503 · 12:04:31<!-- Held in its error state for this page: the script writes data-graph-state, so a live graph
carries data-graph, an id and an aria-label instead, and never this attribute. -->
<div class="graph graph--sm" data-graph-state="error">
<div class="graph-canvas"></div>
<div class="graph-wait skeleton skeleton-block"></div>
<div class="graph-empty graph-empty--error empty-state empty-state--failed">
<i class="ri-error-warning-line state-icon" aria-hidden="true"></i>
<span class="empty-state-title">Could not load the links</span>
<p>The request did not complete. Nothing has been changed.</p>
<span class="empty-state-detail">GET /api/orders/10482/links · 503 · 12:04:31</span>
<button class="btn btn--sm" type="button" data-graph-action="reload"><i class="ri-refresh-line"></i> <span>Try again</span></button>
</div>
</div>
Keyboard and screen readers
Live — tab to the graph, then use the arrow keys. A graph is one stop in the tab
order: the script adds tabindex="0" and role="application" unless the app wrote
its own, and inside it the arrows move a ring from record to record, Page Down walks the links, Space
selects and Enter opens. The name is the app's aria-label, and so is
aria-roledescription="graph", if the word should be said.
Each step is said through the app's [data-graph-live] region, in the sentence in its
data-graph-announce, and a selection in its data-graph-announce-select:
{label}, {kind}, {meta}, any field of the record, and
{links}. It is shown under the graph here so you can read what a screen reader hears; in an
app it is usually .visually-hidden. With the .graph-data list, which reads as the
whole graph in sentences, nothing on the canvas needs a pointer.
- Checkout team
- Search team
- Platform team
- Data team
- Storefront
- Checkout
- Payments
- Search
- Indexer
- Gateway
- Identity
- Data warehouse
- Reports
- The checkout team owns the storefront
- The checkout team owns checkout
- The checkout team owns payments
- The search team owns search
- The search team owns the indexer
- The platform team owns the gateway
- The platform team owns identity
- The data team owns the data warehouse
- The data team owns reports
- The gateway routes to the storefront
- The storefront calls search
- The storefront calls checkout
- Checkout calls payments
- Checkout calls identity
- The indexer reads the data warehouse
- Reports read the data warehouse
Tab to the graph, then move with the arrow keys.
<div class="graph-frame" data-graph-frame>
<div class="card">
<div class="graph" data-graph id="graph-owners" data-graph-arrows="target"
aria-label="Teams and the services they own" aria-roledescription="graph">
<ul class="graph-data" data-graph-data>
<li data-node="checkout-team" data-kind="team" data-shape="hexagon" data-tone="3" data-icon="ri-team-line"
data-meta="Led by Alex Fischer">Checkout team</li>
<li data-node="search-team" data-kind="team" data-shape="hexagon" data-tone="3" data-icon="ri-team-line"
data-meta="Led by Priya Nair">Search team</li>
<li data-node="platform-team" data-kind="team" data-shape="hexagon" data-tone="3" data-icon="ri-team-line"
data-meta="Led by Jordan Weiss">Platform team</li>
<li data-node="data-team" data-kind="team" data-shape="hexagon" data-tone="3" data-icon="ri-team-line"
data-meta="Led by Rin Nakamura">Data team</li>
<li data-node="storefront" data-kind="service" data-tone="2" data-icon="ri-store-2-line" data-meta="Web front end">Storefront</li>
<li data-node="checkout" data-kind="service" data-tone="2" data-icon="ri-secure-payment-line" data-meta="Takes the payment">Checkout</li>
<li data-node="payments" data-kind="service" data-tone="2" data-icon="ri-bank-card-line" data-meta="Talks to the card provider">Payments</li>
<li data-node="search" data-kind="service" data-tone="2" data-icon="ri-search-line" data-meta="Answers product searches">Search</li>
<li data-node="indexer" data-kind="service" data-tone="2" data-icon="ri-refresh-line" data-meta="Rebuilds the index every hour">Indexer</li>
<li data-node="gateway" data-kind="service" data-tone="2" data-icon="ri-door-open-line" data-meta="Routes every request">Gateway</li>
<li data-node="identity" data-kind="service" data-tone="2" data-icon="ri-fingerprint-line" data-meta="Signs customers in">Identity</li>
<li data-node="warehouse" data-kind="service" data-tone="2" data-icon="ri-database-line" data-meta="Holds the reporting data">Data warehouse</li>
<li data-node="reports" data-kind="service" data-tone="2" data-icon="ri-bar-chart-box-line" data-meta="Monthly and daily figures">Reports</li>
<li data-edge data-source="checkout-team" data-target="storefront" data-label="owns">The checkout team owns the storefront</li>
<li data-edge data-source="checkout-team" data-target="checkout" data-label="owns">The checkout team owns checkout</li>
<li data-edge data-source="checkout-team" data-target="payments" data-label="owns">The checkout team owns payments</li>
<li data-edge data-source="search-team" data-target="search" data-label="owns">The search team owns search</li>
<li data-edge data-source="search-team" data-target="indexer" data-label="owns">The search team owns the indexer</li>
<li data-edge data-source="platform-team" data-target="gateway" data-label="owns">The platform team owns the gateway</li>
<li data-edge data-source="platform-team" data-target="identity" data-label="owns">The platform team owns identity</li>
<li data-edge data-source="data-team" data-target="warehouse" data-label="owns">The data team owns the data warehouse</li>
<li data-edge data-source="data-team" data-target="reports" data-label="owns">The data team owns reports</li>
<li data-edge data-source="gateway" data-target="storefront" data-label="routes to" data-line="dashed">The gateway routes to the storefront</li>
<li data-edge data-source="storefront" data-target="search" data-label="calls" data-line="dashed">The storefront calls search</li>
<li data-edge data-source="storefront" data-target="checkout" data-label="calls" data-line="dashed">The storefront calls checkout</li>
<li data-edge data-source="checkout" data-target="payments" data-label="calls" data-line="dashed">Checkout calls payments</li>
<li data-edge data-source="checkout" data-target="identity" data-label="calls" data-line="dashed">Checkout calls identity</li>
<li data-edge data-source="indexer" data-target="warehouse" data-label="reads" data-line="dashed">The indexer reads the data warehouse</li>
<li data-edge data-source="reports" data-target="warehouse" data-label="reads" data-line="dashed">Reports read the data warehouse</li>
</ul>
<div class="graph-canvas"></div>
<div class="graph-wait skeleton skeleton-block"></div>
</div>
</div>
<div class="graph-legend">
<span class="graph-key"><span class="graph-swatch graph-swatch--hexagon series-3"></span><span>Team</span></span>
<span class="graph-key"><span class="graph-swatch series-2"></span><span>Service</span></span>
<span class="graph-key"><span class="graph-line graph-line--arrow"></span><span>Owns</span></span>
<span class="graph-key"><span class="graph-line graph-line--dashed graph-line--arrow"></span><span>Calls</span></span>
</div>
<p class="form-hint" data-graph-live aria-live="polite"
data-graph-announce="{label}, {kind}. {meta}. {links} links."
data-graph-announce-select="{label} selected.">Tab to the graph, then move with the arrow keys.</p>
</div>
Keys and pointer
| Pointer | Keyboard | Does |
|---|---|---|
| hover | ← ↑ → ↓ | Lights a record's neighbourhood and shows its tooltip. |
| — | Page Down Page Up | The next or previous linked record: walking the links. |
| — | Home | The root, else the most connected record. |
| click | Space | Selects the record, or clears the selection. |
| double click | Enter | Opens it: sedna-graph-open, then its data-href. |
| right click, long press | the menu key, Shift + F10 | The app's context menu. |
| drag the background, wheel | Shift + arrows, + −, 0 | Pans, zooms, fits everything. |
| — | Esc | Clears the selection, then the ring. |
Record attributes
| Attribute | Does |
|---|---|
data-node="id" | Makes the item a record. Unique among records and links. |
text, data-label | The name on the canvas. |
data-kind | A category, which a filter, a legend and an announcement can name. |
data-tone | 1–6, go, warn, danger, info, muted, brand, accent. The colour of the .series-* class of the same name. |
data-shape, data-icon | What kind of record it is, drawn: a shape, and a Remix Icon class inside it. |
data-weight | Its size. By default, how many links it has. |
data-root, data-hub, data-muted | What the graph is about; named first; drawn quieter. |
data-meta | A second line: the default tooltip, and under the name in a box. |
data-group, data-cluster, data-parent | Its island, its islands' cluster, and the record it is drawn inside — see Graph layouts. |
data-x, data-y | A position, for the preset layout. |
data-href, data-tags, data-display | Where opening it goes; words a filter or a search matches; dot or box for this record alone. |
any other data-* | The app's own field, which a filter, a tooltip and an announcement can name. |
Link attributes
| Attribute | Does |
|---|---|
data-edge, data-source, data-target | Makes the item a link between two record ids. The id on data-edge is optional. |
text, data-label | The sentence a screen reader hears, and the short form the canvas writes. |
data-kind | What an edge.kind filter names. |
data-line, data-weight, data-arrow | solid dashed dotted; light normal heavy or a number; none target source both. |
data-tone, data-muted | As a record's. By default a link is the quiet border colour. |
A link whose end is not a record, a duplicate id and a cycle of parents are left out with a warning in the console, and the rest of the graph is drawn.
Options on the graph
| Attribute | Values |
|---|---|
data-graph-layout | force, islands, rings, concentric, tree, dagre, fcose, grid, circle, preset — see Graph layouts. |
data-graph-direction, data-graph-spacing, data-graph-curve | TB LR BT RL; compact normal loose; bezier straight taxi round-taxi. |
data-graph-nodes | dot, the default, or box. |
data-graph-labels, data-graph-edge-labels | auto all none; hover always none. |
data-graph-arrows | none, the default, target, source, both. |
data-graph-open, data-graph-wheel | dbltap tap none; focus always none. |
data-graph-hover, data-graph-select, data-graph-drag | none keeps the drawing still under the pointer; none turns selection off; false pins the records. |
data-graph-deferred, data-graph-src, data-graph-eager | Records by a call; records fetched as JSON; start at once rather than when scrolled near. |
data-graph-fit-max | How far a fit may zoom in, e.g. 3; 1.25 for dots and 1.1 for boxes unless set. |
data-graph-scale | The same drawing that many times bigger, e.g. 2: names, records, lines and the gaps between them together, and the fit's ceiling with them. |
data-graph-hold | Names keep their size on screen however far the drawing is zoomed out — for a small graph. |
data-graph-renderer | canvas, the default, or webgl for a very large graph — check it on the hardware the app runs on. |
The options that start with a filter, a focus, a colouring or a plugin are on Exploring a graph and Graph plugins.