Connection lost. Reconnecting… attempt 1 of 8
Paused. Your work is held on the server.
Could not reconnect.
This session has expired on the server.
An unhandled error has occurred.
Sedna.UI
v0.31.0 · main

Exploring a graph

tier 3 — a surface

Everything a reader does with a graph that is already drawn: narrowing it to what matters, finding a record, following its links and reading one in full. Every control here is ordinary markup the app writes — a chip, a select, a search field, a menu, a panel — with a data-graph-* attribute, and none of it needs a line of the app's own script. The markup of a graph itself is on Graph.

Which graph a control drives The graph named by data-graph-for on the control or on an element around it, else the graph the control is inside, else the one in its [data-graph-frame]. So a toolbar, a legend and a panel sit anywhere in the frame and find their graph. A toggle button's aria-pressed is flipped by the script; a checkbox, a radio, a select and a text field keep their native state, which a Blazor binding can own. data-graph-action="reset" puts every control back as it was first drawn.

Filter chips that are the legend

Live — switch a kind off. A button.chip with data-graph-filter="kind" and a value is the key and the switch at once, so there is no second legend to keep in step. Its .graph-swatch wears the .series-* class of the kind's tone and the modifier of its shape — --square, --diamond, --triangle, --hexagon — and a chip that is off hides its value. A button.chip with aria-pressed="true" looks chosen by itself; it needs no .chip--active.

data-graph-stats keeps a sentence current in the app's words. .graph-empty--filtered shows when the filters hide every record, and a reset button in it puts every control back.

  • Storefront
  • Checkout API
  • Orders API
  • Payments API
  • Catalogue API
  • Search API
  • Accounts API
  • Shipping API
  • Notifications
  • Orders database
  • Catalogue database
  • Accounts database
  • Search index
  • Order events
  • Mail queue
  • Nightly price import
  • Abandoned-cart reminder
  • Card processor
  • Carrier tracking
  • Mail relay
  • The storefront calls the checkout API
  • The storefront calls the catalogue API
  • The storefront calls the search API
  • The storefront calls the accounts API
  • The checkout API places orders
  • The checkout API charges through the payments API
  • The checkout API asks the shipping API for a quote
  • The orders API writes the orders database
  • The orders API publishes order events
  • Order events feed the notifications service
  • Order events feed the shipping API
  • Notifications put mail on the mail queue
  • The mail queue sends through the mail relay
  • The payments API authorises cards with the card processor
  • The shipping API tracks parcels with the carrier
  • The catalogue API reads the catalogue database
  • The search API queries the search index
  • The accounts API reads the accounts database
  • The nightly price import updates the catalogue database
  • The nightly price import rebuilds the search index
  • The abandoned-cart reminder reads the orders database
  • The abandoned-cart reminder puts mail on the mail queue
Every kind is switched off

Switch a kind back on above, or start again.

<div class="graph-frame" data-graph-frame>
    <div class="toolbar">
        <div class="toolbar-filters">
            <div class="chip-set" role="group" aria-label="Kinds shown">
                <button class="chip" type="button" data-graph-filter="kind" value="service" aria-pressed="true">
                    <span class="graph-swatch series-2"></span><span class="chip-label">Services</span>
                </button>
                <button class="chip" type="button" data-graph-filter="kind" value="database" aria-pressed="true">
                    <span class="graph-swatch graph-swatch--square series-4"></span><span class="chip-label">Databases</span>
                </button>
                <button class="chip" type="button" data-graph-filter="kind" value="queue" aria-pressed="true">
                    <span class="graph-swatch graph-swatch--diamond series-5"></span><span class="chip-label">Queues</span>
                </button>
                <button class="chip" type="button" data-graph-filter="kind" value="job" aria-pressed="true">
                    <span class="graph-swatch graph-swatch--triangle series-3"></span><span class="chip-label">Scheduled jobs</span>
                </button>
                <button class="chip" type="button" data-graph-filter="kind" value="external" aria-pressed="true">
                    <span class="graph-swatch graph-swatch--hexagon series-6"></span><span class="chip-label">Outside services</span>
                </button>
            </div>
        </div>
        <span class="toolbar-count" data-graph-stats="{nodes} of {totalNodes} records"></span>
    </div>
    <div class="card">
        <div class="graph" data-graph id="xp-kinds" data-graph-arrows="target" aria-label="The web shop's services, by kind">
            <ul class="graph-data" data-graph-data>
                <li data-node="storefront" data-kind="service" data-tone="2" data-icon="ri-store-2-line" data-hub>Storefront</li>
                <li data-node="checkout" data-kind="service" data-tone="2" data-icon="ri-shopping-cart-2-line">Checkout API</li>
                <li data-node="orders" data-kind="service" data-tone="2" data-icon="ri-file-list-3-line">Orders API</li>
                <li data-node="payments" data-kind="service" data-tone="2" data-icon="ri-bank-card-line">Payments API</li>
                <li data-node="catalogue" data-kind="service" data-tone="2" data-icon="ri-book-2-line">Catalogue API</li>
                <li data-node="search" data-kind="service" data-tone="2" data-icon="ri-search-line">Search API</li>
                <li data-node="accounts" data-kind="service" data-tone="2" data-icon="ri-user-line">Accounts API</li>
                <li data-node="shipping" data-kind="service" data-tone="2" data-icon="ri-truck-line">Shipping API</li>
                <li data-node="notify" data-kind="service" data-tone="2" data-icon="ri-notification-3-line">Notifications</li>
                <li data-node="orders-db" data-kind="database" data-tone="4" data-shape="square" data-icon="ri-database-2-line">Orders database</li>
                <li data-node="catalogue-db" data-kind="database" data-tone="4" data-shape="square" data-icon="ri-database-2-line">Catalogue database</li>
                <li data-node="accounts-db" data-kind="database" data-tone="4" data-shape="square" data-icon="ri-database-2-line">Accounts database</li>
                <li data-node="search-index" data-kind="database" data-tone="4" data-shape="square" data-icon="ri-database-2-line">Search index</li>
                <li data-node="order-events" data-kind="queue" data-tone="5" data-shape="diamond" data-icon="ri-stack-line">Order events</li>
                <li data-node="mail-queue" data-kind="queue" data-tone="5" data-shape="diamond" data-icon="ri-stack-line">Mail queue</li>
                <li data-node="price-import" data-kind="job" data-tone="3" data-shape="triangle" data-icon="ri-timer-line">Nightly price import</li>
                <li data-node="cart-reminder" data-kind="job" data-tone="3" data-shape="triangle" data-icon="ri-timer-line">Abandoned-cart reminder</li>
                <li data-node="card-processor" data-kind="external" data-tone="6" data-shape="hexagon" data-icon="ri-global-line">Card processor</li>
                <li data-node="carrier" data-kind="external" data-tone="6" data-shape="hexagon" data-icon="ri-global-line">Carrier tracking</li>
                <li data-node="mail-relay" data-kind="external" data-tone="6" data-shape="hexagon" data-icon="ri-global-line">Mail relay</li>

                <li data-edge data-source="storefront" data-target="checkout" data-label="calls">The storefront calls the checkout API</li>
                <li data-edge data-source="storefront" data-target="catalogue" data-label="calls">The storefront calls the catalogue API</li>
                <li data-edge data-source="storefront" data-target="search" data-label="calls">The storefront calls the search API</li>
                <li data-edge data-source="storefront" data-target="accounts" data-label="calls">The storefront calls the accounts API</li>
                <li data-edge data-source="checkout" data-target="orders" data-label="places">The checkout API places orders</li>
                <li data-edge data-source="checkout" data-target="payments" data-label="charges">The checkout API charges through the payments API</li>
                <li data-edge data-source="checkout" data-target="shipping" data-label="quotes">The checkout API asks the shipping API for a quote</li>
                <li data-edge data-source="orders" data-target="orders-db" data-label="writes">The orders API writes the orders database</li>
                <li data-edge data-source="orders" data-target="order-events" data-label="publishes">The orders API publishes order events</li>
                <li data-edge data-source="order-events" data-target="notify" data-label="feeds">Order events feed the notifications service</li>
                <li data-edge data-source="order-events" data-target="shipping" data-label="feeds">Order events feed the shipping API</li>
                <li data-edge data-source="notify" data-target="mail-queue" data-label="enqueues">Notifications put mail on the mail queue</li>
                <li data-edge data-source="mail-queue" data-target="mail-relay" data-label="sends">The mail queue sends through the mail relay</li>
                <li data-edge data-source="payments" data-target="card-processor" data-label="authorises">The payments API authorises cards with the card processor</li>
                <li data-edge data-source="shipping" data-target="carrier" data-label="tracks">The shipping API tracks parcels with the carrier</li>
                <li data-edge data-source="catalogue" data-target="catalogue-db" data-label="reads">The catalogue API reads the catalogue database</li>
                <li data-edge data-source="search" data-target="search-index" data-label="queries">The search API queries the search index</li>
                <li data-edge data-source="accounts" data-target="accounts-db" data-label="reads">The accounts API reads the accounts database</li>
                <li data-edge data-source="price-import" data-target="catalogue-db" data-label="updates">The nightly price import updates the catalogue database</li>
                <li data-edge data-source="price-import" data-target="search-index" data-label="rebuilds">The nightly price import rebuilds the search index</li>
                <li data-edge data-source="cart-reminder" data-target="orders-db" data-label="reads">The abandoned-cart reminder reads the orders database</li>
                <li data-edge data-source="cart-reminder" data-target="mail-queue" data-label="enqueues">The abandoned-cart reminder puts mail on the mail queue</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"><i class="ri-focus-3-line"></i></button>
            </div>
            <div class="graph-wait skeleton skeleton-block"></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">Every kind is switched off</span>
                <p>Switch a kind back on above, or start again.</p>
                <button class="btn btn--sm" type="button" data-graph-action="reset"><i class="ri-filter-off-line"></i> <span>Show every kind</span></button>
            </div>
        </div>
    </div>
</div>

Choosing one value

Live. A <select> or a group of radios with data-graph-filter chooses: only records with that value stay, and value="" chooses all. A record without the field is not what was chosen, so it goes — the gateway and the message bus, which no team owns, leave as soon as a team is chosen. A value with spaces chooses each of its words: “Needs attention” is value="degraded down".

That is the difference from a chip, which is a switch and hides only what it names. Choose Platform and Needs attention together to see the empty state say so in the app's words.

  • API gateway
  • Message bus
  • Basket
  • Checkout API
  • Payments
  • Vouchers
  • Fraud screening
  • Product API
  • Pricing
  • Search
  • Reviews
  • Media store
  • Orders API
  • Warehouse sync
  • Shipping
  • Returns
  • Label printing
  • Single sign-on
  • Feature flags
  • Metrics
  • Log store
  • The gateway routes to the basket
  • The gateway routes to the checkout API
  • The gateway routes to the product API
  • The gateway routes to the orders API
  • The gateway checks every call with single sign-on
  • The basket prices its lines with pricing
  • The checkout API reads the basket
  • The checkout API charges through payments
  • The checkout API redeems vouchers
  • Payments screens every charge for fraud
  • The checkout API places orders
  • The product API asks pricing for prices
  • The product API reads the media store
  • The product API shows reviews
  • Search indexes the product API
  • The orders API publishes to the message bus
  • The message bus feeds the warehouse sync
  • The message bus feeds shipping
  • Shipping prints parcel labels
  • Returns refunds through the orders API
  • Returns prints return labels
  • Feature flags configure the checkout API
  • Feature flags configure search
  • Metrics watches the gateway
  • Metrics writes to the log store
Nothing needs attention here

Every service this team runs is healthy.

Checkout Catalogue Fulfilment Platform Shared by every team
<div class="graph-frame" data-graph-frame>
    <div class="toolbar">
        <div class="toolbar-filters">
            <select class="form-select form-select--sm toolbar-input" data-graph-filter="team" aria-label="Team">
                <option value="" selected>Every team</option>
                <option value="checkout">Checkout</option>
                <option value="catalogue">Catalogue</option>
                <option value="fulfilment">Fulfilment</option>
                <option value="platform">Platform</option>
            </select>
            <div class="segmented segmented--sm" role="group" aria-label="Health">
                <label class="segmented-option"><input type="radio" name="xp-choose-health" data-graph-filter="status" value="" checked /> <span>All</span></label>
                <label class="segmented-option"><input type="radio" name="xp-choose-health" data-graph-filter="status" value="healthy" /> <span>Healthy</span></label>
                <label class="segmented-option"><input type="radio" name="xp-choose-health" data-graph-filter="status" value="degraded down" /> <span>Needs attention</span></label>
            </div>
        </div>
        <span class="toolbar-count" data-graph-stats="{nodes} of {totalNodes} services"></span>
    </div>
    <div class="card">
        <div class="graph" data-graph id="xp-choose" data-graph-arrows="target" aria-label="Services by the team that runs them">
            <ul class="graph-data" data-graph-data>
                <li data-node="gateway" data-tone="muted" data-shape="hexagon" data-icon="ri-door-open-line" data-status="healthy" data-meta="Shared by every team" data-hub>API gateway</li>
                <li data-node="bus" data-tone="muted" data-shape="diamond" data-icon="ri-stack-line" data-status="healthy" data-meta="Shared by every team">Message bus</li>

                <li data-node="basket" data-team="checkout" data-tone="2" data-status="healthy" data-meta="Checkout">Basket</li>
                <li data-node="checkout" data-team="checkout" data-tone="2" data-status="healthy" data-meta="Checkout" data-hub>Checkout API</li>
                <li data-node="payments" data-team="checkout" data-tone="2" data-status="healthy" data-meta="Checkout">Payments</li>
                <li data-node="vouchers" data-team="checkout" data-tone="2" data-status="healthy" data-meta="Checkout">Vouchers</li>
                <li data-node="fraud" data-team="checkout" data-tone="2" data-status="degraded" data-meta="Checkout · slow to answer">Fraud screening</li>

                <li data-node="products" data-team="catalogue" data-tone="3" data-status="healthy" data-meta="Catalogue" data-hub>Product API</li>
                <li data-node="pricing" data-team="catalogue" data-tone="3" data-status="healthy" data-meta="Catalogue">Pricing</li>
                <li data-node="search" data-team="catalogue" data-tone="3" data-status="degraded" data-meta="Catalogue · index rebuilding">Search</li>
                <li data-node="reviews" data-team="catalogue" data-tone="3" data-status="healthy" data-meta="Catalogue">Reviews</li>
                <li data-node="media" data-team="catalogue" data-tone="3" data-shape="square" data-status="healthy" data-meta="Catalogue">Media store</li>

                <li data-node="orders" data-team="fulfilment" data-tone="5" data-status="healthy" data-meta="Fulfilment" data-hub>Orders API</li>
                <li data-node="warehouse" data-team="fulfilment" data-tone="5" data-status="down" data-meta="Fulfilment · not answering">Warehouse sync</li>
                <li data-node="shipping" data-team="fulfilment" data-tone="5" data-status="healthy" data-meta="Fulfilment">Shipping</li>
                <li data-node="returns" data-team="fulfilment" data-tone="5" data-status="healthy" data-meta="Fulfilment">Returns</li>
                <li data-node="labels" data-team="fulfilment" data-tone="5" data-status="healthy" data-meta="Fulfilment">Label printing</li>

                <li data-node="sign-in" data-team="platform" data-tone="4" data-status="healthy" data-meta="Platform" data-hub>Single sign-on</li>
                <li data-node="flags" data-team="platform" data-tone="4" data-status="healthy" data-meta="Platform">Feature flags</li>
                <li data-node="metrics" data-team="platform" data-tone="4" data-status="healthy" data-meta="Platform">Metrics</li>
                <li data-node="logs" data-team="platform" data-tone="4" data-shape="square" data-status="healthy" data-meta="Platform">Log store</li>

                <li data-edge data-source="gateway" data-target="basket" data-label="routes">The gateway routes to the basket</li>
                <li data-edge data-source="gateway" data-target="checkout" data-label="routes">The gateway routes to the checkout API</li>
                <li data-edge data-source="gateway" data-target="products" data-label="routes">The gateway routes to the product API</li>
                <li data-edge data-source="gateway" data-target="orders" data-label="routes">The gateway routes to the orders API</li>
                <li data-edge data-source="gateway" data-target="sign-in" data-label="checks">The gateway checks every call with single sign-on</li>
                <li data-edge data-source="basket" data-target="pricing" data-label="prices">The basket prices its lines with pricing</li>
                <li data-edge data-source="checkout" data-target="basket" data-label="reads">The checkout API reads the basket</li>
                <li data-edge data-source="checkout" data-target="payments" data-label="charges">The checkout API charges through payments</li>
                <li data-edge data-source="checkout" data-target="vouchers" data-label="redeems">The checkout API redeems vouchers</li>
                <li data-edge data-source="payments" data-target="fraud" data-label="screens">Payments screens every charge for fraud</li>
                <li data-edge data-source="checkout" data-target="orders" data-label="places">The checkout API places orders</li>
                <li data-edge data-source="products" data-target="pricing" data-label="asks">The product API asks pricing for prices</li>
                <li data-edge data-source="products" data-target="media" data-label="reads">The product API reads the media store</li>
                <li data-edge data-source="products" data-target="reviews" data-label="shows">The product API shows reviews</li>
                <li data-edge data-source="search" data-target="products" data-label="indexes">Search indexes the product API</li>
                <li data-edge data-source="orders" data-target="bus" data-label="publishes">The orders API publishes to the message bus</li>
                <li data-edge data-source="bus" data-target="warehouse" data-label="feeds">The message bus feeds the warehouse sync</li>
                <li data-edge data-source="bus" data-target="shipping" data-label="feeds">The message bus feeds shipping</li>
                <li data-edge data-source="shipping" data-target="labels" data-label="prints">Shipping prints parcel labels</li>
                <li data-edge data-source="returns" data-target="orders" data-label="refunds">Returns refunds through the orders API</li>
                <li data-edge data-source="returns" data-target="labels" data-label="prints">Returns prints return labels</li>
                <li data-edge data-source="flags" data-target="checkout" data-label="configures">Feature flags configure the checkout API</li>
                <li data-edge data-source="flags" data-target="search" data-label="configures">Feature flags configure search</li>
                <li data-edge data-source="metrics" data-target="gateway" data-label="watches">Metrics watches the gateway</li>
                <li data-edge data-source="metrics" data-target="logs" data-label="writes">Metrics writes to the log store</li>
            </ul>
            <div class="graph-canvas"></div>
            <div class="graph-wait skeleton skeleton-block"></div>
            <div class="graph-empty graph-empty--filtered empty-state">
                <svg class="state-art" aria-hidden="true"><use href="#all-done" /></svg>
                <span class="empty-state-title">Nothing needs attention here</span>
                <p>Every service this team runs is healthy.</p>
            </div>
        </div>
    </div>
    <div class="graph-legend">
        <span class="graph-key"><span class="graph-swatch series-2"></span> <span>Checkout</span></span>
        <span class="graph-key"><span class="graph-swatch series-3"></span> <span>Catalogue</span></span>
        <span class="graph-key"><span class="graph-swatch series-5"></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-swatch series-muted"></span> <span>Shared by every team</span></span>
    </div>
</div>

Muted and unlinked records

Live. data-muted draws a record quieter — retired, archived, out of scope — and data-graph-show="muted" on a checkbox or a switch shows or hides all of them. data-graph-show="isolated" shows the records with no visible link, and while it is off they go with whatever took their last link away: hide the retired applications and the receipt archive, fed only by paper forms, goes too.

Both start as their control says, so this graph opens without the three applications that stand alone. .graph-swatch--muted is the key for a quieter record.

  • HR portal
  • Payroll
  • Time tracking
  • Learning platform
  • Finance ledger
  • Expense claims
  • Invoice scanning
  • Supplier portal
  • Intranet
  • Room booking
  • Visitor sign-in
  • Canteen menu
  • Parking permits
  • Old payroll
  • Paper expense forms
  • Receipt archive
  • The HR portal sends hires and leavers to payroll
  • Time tracking sends hours to payroll
  • The HR portal enrols new starters in the learning platform
  • The HR portal sends rosters to time tracking
  • Payroll posts salaries to the finance ledger
  • Expense claims post to the finance ledger
  • Invoice scanning posts invoices to the finance ledger
  • Suppliers upload invoices through the supplier portal
  • The intranet links to the HR portal
  • The intranet links to room booking
  • The intranet links to expense claims
  • Old payroll posted salaries to the finance ledger
  • Old payroll was replaced by payroll
  • Paper expense forms were filed in the receipt archive
  • Paper expense forms were replaced by expense claims
People Finance Workplace Retired, drawn quieter
<div class="graph-frame" data-graph-frame>
    <div class="toolbar">
        <div class="toolbar-filters">
            <label class="switch"><input type="checkbox" data-graph-show="muted" checked /> <span>Retired applications</span></label>
            <label class="switch"><input type="checkbox" data-graph-show="isolated" /> <span>Applications with no links</span></label>
        </div>
        <span class="toolbar-count" data-graph-stats="{nodes} of {totalNodes} applications"></span>
    </div>
    <div class="card">
        <div class="graph" data-graph id="xp-muted" aria-label="Head-office applications and what they exchange">
            <ul class="graph-data" data-graph-data>
                <li data-node="hr-portal" data-tone="2" data-icon="ri-team-line" data-meta="People" data-hub>HR portal</li>
                <li data-node="payroll" data-tone="2" data-icon="ri-money-euro-circle-line" data-meta="People">Payroll</li>
                <li data-node="time" data-tone="2" data-icon="ri-time-line" data-meta="People">Time tracking</li>
                <li data-node="learning" data-tone="2" data-icon="ri-graduation-cap-line" data-meta="People">Learning platform</li>
                <li data-node="ledger" data-tone="4" data-shape="square" data-icon="ri-book-3-line" data-meta="Finance" data-hub>Finance ledger</li>
                <li data-node="expenses" data-tone="4" data-shape="square" data-icon="ri-receipt-line" data-meta="Finance">Expense claims</li>
                <li data-node="invoices" data-tone="4" data-shape="square" data-icon="ri-scan-2-line" data-meta="Finance">Invoice scanning</li>
                <li data-node="suppliers" data-tone="4" data-shape="square" data-icon="ri-building-line" data-meta="Finance">Supplier portal</li>
                <li data-node="intranet" data-tone="5" data-shape="diamond" data-icon="ri-home-4-line" data-meta="Workplace" data-hub>Intranet</li>
                <li data-node="rooms" data-tone="5" data-shape="diamond" data-icon="ri-calendar-check-line" data-meta="Workplace">Room booking</li>
                <li data-node="visitors" data-tone="5" data-shape="diamond" data-icon="ri-user-add-line" data-meta="Workplace · stands alone">Visitor sign-in</li>
                <li data-node="canteen" data-tone="5" data-shape="diamond" data-icon="ri-restaurant-line" data-meta="Workplace · stands alone">Canteen menu</li>
                <li data-node="parking" data-tone="5" data-shape="diamond" data-icon="ri-parking-box-line" data-meta="Workplace · stands alone">Parking permits</li>
                <li data-node="old-payroll" data-tone="2" data-icon="ri-money-euro-circle-line" data-meta="Retired in March" data-muted>Old payroll</li>
                <li data-node="old-expenses" data-tone="4" data-shape="square" data-icon="ri-receipt-line" data-meta="Retired last year" data-muted>Paper expense forms</li>
                <li data-node="receipts" data-tone="4" data-shape="square" data-icon="ri-archive-line" data-meta="Finance · fed only by paper forms">Receipt archive</li>

                <li data-edge data-source="hr-portal" data-target="payroll" data-label="hires and leavers">The HR portal sends hires and leavers to payroll</li>
                <li data-edge data-source="time" data-target="payroll" data-label="hours">Time tracking sends hours to payroll</li>
                <li data-edge data-source="hr-portal" data-target="learning" data-label="enrols">The HR portal enrols new starters in the learning platform</li>
                <li data-edge data-source="hr-portal" data-target="time" data-label="rosters">The HR portal sends rosters to time tracking</li>
                <li data-edge data-source="payroll" data-target="ledger" data-label="salaries">Payroll posts salaries to the finance ledger</li>
                <li data-edge data-source="expenses" data-target="ledger" data-label="claims">Expense claims post to the finance ledger</li>
                <li data-edge data-source="invoices" data-target="ledger" data-label="invoices">Invoice scanning posts invoices to the finance ledger</li>
                <li data-edge data-source="suppliers" data-target="invoices" data-label="uploads">Suppliers upload invoices through the supplier portal</li>
                <li data-edge data-source="intranet" data-target="hr-portal" data-label="links to">The intranet links to the HR portal</li>
                <li data-edge data-source="intranet" data-target="rooms" data-label="links to">The intranet links to room booking</li>
                <li data-edge data-source="intranet" data-target="expenses" data-label="links to">The intranet links to expense claims</li>
                <li data-edge data-source="old-payroll" data-target="ledger" data-label="salaries" data-muted>Old payroll posted salaries to the finance ledger</li>
                <li data-edge data-source="old-payroll" data-target="payroll" data-label="replaced by" data-line="dotted" data-muted>Old payroll was replaced by payroll</li>
                <li data-edge data-source="old-expenses" data-target="receipts" data-label="files" data-muted>Paper expense forms were filed in the receipt archive</li>
                <li data-edge data-source="old-expenses" data-target="expenses" data-label="replaced by" data-line="dotted" data-muted>Paper expense forms were replaced by expense claims</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 series-2"></span> <span>People</span></span>
        <span class="graph-key"><span class="graph-swatch graph-swatch--square series-4"></span> <span>Finance</span></span>
        <span class="graph-key"><span class="graph-swatch graph-swatch--diamond series-5"></span> <span>Workplace</span></span>
        <span class="graph-key"><span class="graph-swatch graph-swatch--muted"></span> <span>Retired, drawn quieter</span></span>
    </div>
</div>

Live. data-graph-filter="edge.kind" filters links by their data-kind. A link's key is a .graph-line — --dashed, --dotted, --light, --heavy, --arrow — in the .series-* colour its data-tone paints, with the same data-line and data-weight on the links, so a line style carries the difference as well as the colour.

A chip written aria-pressed="false" starts off: monitoring stays hidden until it is asked for. A record keeps its place when its links go; untick data-graph-show="isolated" beside the chips to take away what is left with none.

<div class="graph-frame" data-graph-frame>
    <div class="toolbar">
        <div class="toolbar-filters">
            <div class="chip-set" role="group" aria-label="Links shown">
                <button class="chip" type="button" data-graph-filter="edge.kind" value="call" aria-pressed="true">
                    <span class="graph-line graph-line--arrow series-1"></span><span class="chip-label">Calls</span>
                </button>
                <button class="chip" type="button" data-graph-filter="edge.kind" value="event" aria-pressed="true">
                    <span class="graph-line graph-line--dotted graph-line--arrow series-5"></span><span class="chip-label">Events</span>
                </button>
                <button class="chip" type="button" data-graph-filter="edge.kind" value="data" aria-pressed="true">
                    <span class="graph-line graph-line--dashed graph-line--arrow series-4"></span><span class="chip-label">Reads and writes</span>
                </button>
                <button class="chip" type="button" data-graph-filter="edge.kind" value="replication" aria-pressed="true">
                    <span class="graph-line graph-line--heavy series-3"></span><span class="chip-label">Replication</span>
                </button>
                <button class="chip" type="button" data-graph-filter="edge.kind" value="watch" aria-pressed="false">
                    <span class="graph-line graph-line--light series-muted"></span><span class="chip-label">Monitoring</span>
                </button>
            </div>
            <label class="form-check"><input type="checkbox" data-graph-show="isolated" checked /> <span>Records left with no link</span></label>
        </div>
        <span class="toolbar-count" data-graph-stats="{edges} of {totalEdges} links"></span>
    </div>
    <div class="card">
        <div class="graph" data-graph id="xp-links" data-graph-arrows="target" aria-label="How an order travels between services">
            <ul class="graph-data" data-graph-data>
                <li data-node="storefront" data-tone="2" data-icon="ri-store-2-line" data-hub>Storefront</li>
                <li data-node="checkout" data-tone="2" data-icon="ri-shopping-cart-2-line">Checkout API</li>
                <li data-node="orders" data-tone="2" data-icon="ri-file-list-3-line" data-hub>Orders API</li>
                <li data-node="payments" data-tone="2" data-icon="ri-bank-card-line">Payments API</li>
                <li data-node="stock" data-tone="2" data-icon="ri-archive-stack-line">Stock service</li>
                <li data-node="invoicing" data-tone="2" data-icon="ri-bill-line">Invoicing</li>
                <li data-node="warehouse" data-tone="2" data-icon="ri-inbox-archive-line">Warehouse app</li>
                <li data-node="mail" data-tone="2" data-icon="ri-mail-send-line">Mail service</li>
                <li data-node="events" data-tone="5" data-shape="diamond" data-icon="ri-stack-line">Order events</li>
                <li data-node="orders-db" data-tone="4" data-shape="square" data-icon="ri-database-2-line">Orders database</li>
                <li data-node="orders-replica" data-tone="4" data-shape="square" data-icon="ri-database-2-line">Orders replica</li>
                <li data-node="stock-db" data-tone="4" data-shape="square" data-icon="ri-database-2-line">Stock database</li>
                <li data-node="reporting" data-tone="6" data-shape="triangle" data-icon="ri-bar-chart-2-line">Reporting</li>
                <li data-node="metrics" data-tone="muted" data-shape="hexagon" data-icon="ri-pulse-line">Metrics</li>

                <li data-edge data-source="storefront" data-target="checkout" data-kind="call" data-tone="1" data-label="calls">The storefront calls the checkout API</li>
                <li data-edge data-source="checkout" data-target="orders" data-kind="call" data-tone="1" data-label="places">The checkout API places the order with the orders API</li>
                <li data-edge data-source="checkout" data-target="payments" data-kind="call" data-tone="1" data-label="charges">The checkout API charges through the payments API</li>
                <li data-edge data-source="checkout" data-target="stock" data-kind="call" data-tone="1" data-label="reserves">The checkout API reserves stock</li>
                <li data-edge data-source="orders" data-target="events" data-kind="event" data-tone="5" data-line="dotted" data-label="order placed">The orders API publishes an order-placed event</li>
                <li data-edge data-source="events" data-target="invoicing" data-kind="event" data-tone="5" data-line="dotted" data-label="order placed">Invoicing hears every order placed</li>
                <li data-edge data-source="events" data-target="warehouse" data-kind="event" data-tone="5" data-line="dotted" data-label="order placed">The warehouse app hears every order placed</li>
                <li data-edge data-source="events" data-target="mail" data-kind="event" data-tone="5" data-line="dotted" data-label="order placed">The mail service hears every order placed</li>
                <li data-edge data-source="orders" data-target="orders-db" data-kind="data" data-tone="4" data-line="dashed" data-label="writes">The orders API writes the orders database</li>
                <li data-edge data-source="stock" data-target="stock-db" data-kind="data" data-tone="4" data-line="dashed" data-label="writes">The stock service writes the stock database</li>
                <li data-edge data-source="warehouse" data-target="stock-db" data-kind="data" data-tone="4" data-line="dashed" data-label="reads">The warehouse app reads the stock database</li>
                <li data-edge data-source="reporting" data-target="orders-replica" data-kind="data" data-tone="4" data-line="dashed" data-label="reads">Reporting reads the orders replica</li>
                <li data-edge data-source="orders-db" data-target="orders-replica" data-kind="replication" data-tone="3" data-weight="heavy" data-arrow="none" data-label="replicates">The orders database replicates to the orders replica</li>
                <li data-edge data-source="metrics" data-target="checkout" data-kind="watch" data-tone="muted" data-weight="light" data-label="watches">Metrics watches the checkout API</li>
                <li data-edge data-source="metrics" data-target="orders" data-kind="watch" data-tone="muted" data-weight="light" data-label="watches">Metrics watches the orders API</li>
                <li data-edge data-source="metrics" data-target="payments" data-kind="watch" data-tone="muted" data-weight="light" data-label="watches">Metrics watches the payments API</li>
                <li data-edge data-source="metrics" data-target="events" data-kind="watch" data-tone="muted" data-weight="light" data-label="watches">Metrics watches the order events</li>
            </ul>
            <div class="graph-canvas"></div>
            <div class="graph-wait skeleton skeleton-block"></div>
        </div>
    </div>
</div>

Live — try “refund”, then “login code”. A text field with data-graph-search marks records as it is typed in, and once typing pauses the view settles on the matches and what they link to. It marks rather than hides, because where a record sits among the others is what a graph is for. Every word has to appear in a record's name, its id, its data-meta, its kind or its data-tags.

data-graph-stats-match replaces the stats sentence while a search is marking records. The mark is a ring in the accent colour, so give the records of a graph that is searched tones that are not close to it.

Topic Article Video See also
<div class="graph-frame" data-graph-frame>
    <div class="toolbar">
        <div class="toolbar-filters">
            <input class="form-input form-input--sm toolbar-input toolbar-input--wide" type="search" data-graph-search
                   placeholder="Search the help centre, e.g. refund" aria-label="Search the help centre" />
        </div>
        <span class="toolbar-count" data-graph-stats="{nodes} articles and topics" data-graph-stats-match="{matches} of {nodes} match"></span>
    </div>
    <div class="card">
        <div class="graph" data-graph id="xp-search" aria-label="Help-centre articles and the topics they are filed under">
            <ul class="graph-data" data-graph-data>
                <li data-node="accounts" data-kind="topic" data-tone="3" data-shape="star" data-meta="Topic" data-hub>Accounts</li>
                <li data-node="billing" data-kind="topic" data-tone="3" data-shape="star" data-meta="Topic" data-hub>Billing</li>
                <li data-node="security" data-kind="topic" data-tone="3" data-shape="star" data-meta="Topic" data-hub>Security</li>
                <li data-node="orders" data-kind="topic" data-tone="3" data-shape="star" data-meta="Topic" data-hub>Orders</li>
                <li data-node="delivery" data-kind="topic" data-tone="3" data-shape="star" data-meta="Topic" data-hub>Delivery</li>

                <li data-node="reset-password" data-kind="article" data-tone="4" data-tags="password login sign-in" data-meta="Article · 2 min read">Reset your password</li>
                <li data-node="two-step" data-kind="article" data-tone="4" data-tags="mfa login code authenticator" data-meta="Article · 4 min read">Turn on two-step sign-in</li>
                <li data-node="two-step-video" data-kind="video" data-tone="5" data-shape="tag" data-tags="mfa login code authenticator" data-meta="Video · 3 minutes">Setting up two-step sign-in</li>
                <li data-node="change-email" data-kind="article" data-tone="4" data-tags="email profile login" data-meta="Article · 2 min read">Change the e-mail address on an account</li>
                <li data-node="close-account" data-kind="article" data-tone="4" data-tags="delete privacy data" data-meta="Article · 3 min read">Close an account</li>
                <li data-node="phishing" data-kind="article" data-tone="4" data-tags="email scam fraud" data-meta="Article · 5 min read">Spot a phishing e-mail</li>
                <li data-node="invoice" data-kind="article" data-tone="4" data-tags="invoice pdf receipt vat" data-meta="Article · 1 min read">Download an invoice</li>
                <li data-node="card" data-kind="article" data-tone="4" data-tags="card payment expiry" data-meta="Article · 2 min read">Update a payment card</li>
                <li data-node="charged-twice" data-kind="article" data-tone="4" data-tags="refund charge payment pending" data-meta="Article · 3 min read">Why was I charged twice?</li>
                <li data-node="refund" data-kind="article" data-tone="4" data-tags="refund return money" data-meta="Article · 3 min read">Request a refund</li>
                <li data-node="cancel" data-kind="article" data-tone="4" data-tags="cancel order refund" data-meta="Article · 2 min read">Cancel an order</li>
                <li data-node="return" data-kind="article" data-tone="4" data-tags="return refund label parcel" data-meta="Article · 4 min read">Return an item</li>
                <li data-node="return-video" data-kind="video" data-tone="5" data-shape="tag" data-tags="return label parcel" data-meta="Video · 2 minutes">Printing a return label</li>
                <li data-node="track" data-kind="article" data-tone="4" data-tags="tracking parcel carrier late" data-meta="Article · 2 min read">Track a parcel</li>
                <li data-node="address" data-kind="article" data-tone="4" data-tags="address delivery move" data-meta="Article · 2 min read">Change a delivery address</li>
                <li data-node="missing" data-kind="article" data-tone="4" data-tags="parcel lost late refund" data-meta="Article · 3 min read">A parcel has not arrived</li>

                <li data-edge data-source="reset-password" data-target="accounts" data-label="filed under">Reset your password is filed under Accounts</li>
                <li data-edge data-source="reset-password" data-target="security" data-label="filed under">Reset your password is filed under Security</li>
                <li data-edge data-source="two-step" data-target="security" data-label="filed under">Turn on two-step sign-in is filed under Security</li>
                <li data-edge data-source="two-step-video" data-target="two-step" data-label="shows">The video shows how to turn on two-step sign-in</li>
                <li data-edge data-source="change-email" data-target="accounts" data-label="filed under">Change the e-mail address is filed under Accounts</li>
                <li data-edge data-source="close-account" data-target="accounts" data-label="filed under">Close an account is filed under Accounts</li>
                <li data-edge data-source="phishing" data-target="security" data-label="filed under">Spot a phishing e-mail is filed under Security</li>
                <li data-edge data-source="invoice" data-target="billing" data-label="filed under">Download an invoice is filed under Billing</li>
                <li data-edge data-source="card" data-target="billing" data-label="filed under">Update a payment card is filed under Billing</li>
                <li data-edge data-source="charged-twice" data-target="billing" data-label="filed under">Why was I charged twice is filed under Billing</li>
                <li data-edge data-source="refund" data-target="billing" data-label="filed under">Request a refund is filed under Billing</li>
                <li data-edge data-source="refund" data-target="orders" data-label="filed under">Request a refund is filed under Orders</li>
                <li data-edge data-source="cancel" data-target="orders" data-label="filed under">Cancel an order is filed under Orders</li>
                <li data-edge data-source="return" data-target="orders" data-label="filed under">Return an item is filed under Orders</li>
                <li data-edge data-source="return" data-target="delivery" data-label="filed under">Return an item is filed under Delivery</li>
                <li data-edge data-source="return-video" data-target="return" data-label="shows">The video shows how to print a return label</li>
                <li data-edge data-source="track" data-target="delivery" data-label="filed under">Track a parcel is filed under Delivery</li>
                <li data-edge data-source="address" data-target="delivery" data-label="filed under">Change a delivery address is filed under Delivery</li>
                <li data-edge data-source="address" data-target="orders" data-label="filed under">Change a delivery address is filed under Orders</li>
                <li data-edge data-source="missing" data-target="delivery" data-label="filed under">A parcel has not arrived is filed under Delivery</li>
                <li data-edge data-source="charged-twice" data-target="refund" data-label="see also" data-line="dashed">Why was I charged twice points to Request a refund</li>
                <li data-edge data-source="missing" data-target="track" data-label="see also" data-line="dashed">A parcel has not arrived points to Track a parcel</li>
                <li data-edge data-source="phishing" data-target="two-step" data-label="see also" data-line="dashed">Spot a phishing e-mail points to Turn on two-step sign-in</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--star series-3"></span> <span>Topic</span></span>
        <span class="graph-key"><span class="graph-swatch series-4"></span> <span>Article</span></span>
        <span class="graph-key"><span class="graph-swatch graph-swatch--tag series-5"></span> <span>Video</span></span>
        <span class="graph-key"><span class="graph-line graph-line--dashed"></span> <span>See also</span></span>
    </div>
</div>

A record's neighbourhood

Live — reach two steps out, then right-click http-client. A neighbourhood is one record and what it touches, and nothing else. data-graph-focus starts the graph on one, and data-graph-focus-depth or a data-graph-option="depth" control says how many links out to reach. The focus action moves to another record — from the menu, the side panel or a button — and unfocus shows everything again.

The record in focus is ringed and never filtered away, and the filters still apply around it.

  • web-shop
  • admin-console
  • mobile-web
  • checkout-ui
  • catalogue-ui
  • account-ui
  • ui-kit
  • form-kit
  • charts
  • design-tokens
  • http-client
  • auth-client
  • api-schema
  • i18n
  • money
  • date-utils
  • analytics
  • feature-flags
  • logging
  • config
  • web-shop depends on checkout-ui
  • web-shop depends on catalogue-ui
  • web-shop depends on account-ui
  • web-shop depends on ui-kit
  • web-shop depends on analytics
  • web-shop depends on feature-flags
  • mobile-web depends on checkout-ui
  • mobile-web depends on catalogue-ui
  • mobile-web depends on ui-kit
  • admin-console depends on ui-kit
  • admin-console depends on form-kit
  • admin-console depends on charts
  • admin-console depends on auth-client
  • checkout-ui depends on ui-kit
  • checkout-ui depends on form-kit
  • checkout-ui depends on http-client
  • checkout-ui depends on money
  • checkout-ui depends on analytics
  • catalogue-ui depends on ui-kit
  • catalogue-ui depends on http-client
  • catalogue-ui depends on money
  • account-ui depends on form-kit
  • account-ui depends on auth-client
  • ui-kit depends on design-tokens
  • ui-kit depends on i18n
  • form-kit depends on ui-kit
  • form-kit depends on date-utils
  • charts depends on design-tokens
  • charts depends on date-utils
  • http-client depends on api-schema
  • http-client depends on auth-client
  • http-client depends on logging
  • auth-client depends on config
  • auth-client depends on logging
  • analytics depends on config
  • feature-flags depends on http-client
  • feature-flags depends on config
  • money depends on i18n
  • date-utils depends on i18n
Application Feature package Shared interface Library Depends on
<div class="graph-frame" data-graph-frame>
    <div class="toolbar">
        <div class="toolbar-filters">
            <div class="segmented segmented--sm" role="group" aria-label="How far to reach">
                <label class="segmented-option"><input type="radio" name="xp-reach" data-graph-option="depth" value="1" checked /> <span>1 step</span></label>
                <label class="segmented-option"><input type="radio" name="xp-reach" data-graph-option="depth" value="2" /> <span>2 steps</span></label>
                <label class="segmented-option"><input type="radio" name="xp-reach" data-graph-option="depth" value="3" /> <span>3 steps</span></label>
            </div>
            <button class="btn btn--sm" type="button" data-graph-action="unfocus"><i class="ri-fullscreen-exit-line"></i> <span>Every package</span></button>
        </div>
        <span class="toolbar-count" data-graph-stats="{nodes} of {totalNodes} packages"></span>
    </div>
    <div class="card">
        <div class="graph" data-graph id="xp-reach" data-graph-focus="checkout-ui" data-graph-focus-depth="1"
             data-graph-arrows="target" aria-label="Packages of the web shop and what each depends on">
            <ul class="graph-data" data-graph-data>
                <li data-node="web-shop" data-kind="application" data-tone="2" data-shape="square" data-meta="Application" data-hub>web-shop</li>
                <li data-node="admin-console" data-kind="application" data-tone="2" data-shape="square" data-meta="Application" data-hub>admin-console</li>
                <li data-node="mobile-web" data-kind="application" data-tone="2" data-shape="square" data-meta="Application">mobile-web</li>
                <li data-node="checkout-ui" data-kind="feature" data-tone="3" data-meta="Feature package">checkout-ui</li>
                <li data-node="catalogue-ui" data-kind="feature" data-tone="3" data-meta="Feature package">catalogue-ui</li>
                <li data-node="account-ui" data-kind="feature" data-tone="3" data-meta="Feature package">account-ui</li>
                <li data-node="ui-kit" data-kind="ui" data-tone="5" data-shape="diamond" data-meta="Shared interface">ui-kit</li>
                <li data-node="form-kit" data-kind="ui" data-tone="5" data-shape="diamond" data-meta="Shared interface">form-kit</li>
                <li data-node="charts" data-kind="ui" data-tone="5" data-shape="diamond" data-meta="Shared interface">charts</li>
                <li data-node="design-tokens" data-kind="ui" data-tone="5" data-shape="diamond" data-meta="Shared interface">design-tokens</li>
                <li data-node="http-client" data-kind="library" data-tone="6" data-shape="hexagon" data-meta="Library">http-client</li>
                <li data-node="auth-client" data-kind="library" data-tone="6" data-shape="hexagon" data-meta="Library">auth-client</li>
                <li data-node="api-schema" data-kind="library" data-tone="6" data-shape="hexagon" data-meta="Library">api-schema</li>
                <li data-node="i18n" data-kind="library" data-tone="6" data-shape="hexagon" data-meta="Library">i18n</li>
                <li data-node="money" data-kind="library" data-tone="6" data-shape="hexagon" data-meta="Library">money</li>
                <li data-node="date-utils" data-kind="library" data-tone="6" data-shape="hexagon" data-meta="Library">date-utils</li>
                <li data-node="analytics" data-kind="library" data-tone="6" data-shape="hexagon" data-meta="Library">analytics</li>
                <li data-node="feature-flags" data-kind="library" data-tone="6" data-shape="hexagon" data-meta="Library">feature-flags</li>
                <li data-node="logging" data-kind="library" data-tone="6" data-shape="hexagon" data-meta="Library">logging</li>
                <li data-node="config" data-kind="library" data-tone="6" data-shape="hexagon" data-meta="Library">config</li>

                <li data-edge data-source="web-shop" data-target="checkout-ui" data-label="depends on">web-shop depends on checkout-ui</li>
                <li data-edge data-source="web-shop" data-target="catalogue-ui" data-label="depends on">web-shop depends on catalogue-ui</li>
                <li data-edge data-source="web-shop" data-target="account-ui" data-label="depends on">web-shop depends on account-ui</li>
                <li data-edge data-source="web-shop" data-target="ui-kit" data-label="depends on">web-shop depends on ui-kit</li>
                <li data-edge data-source="web-shop" data-target="analytics" data-label="depends on">web-shop depends on analytics</li>
                <li data-edge data-source="web-shop" data-target="feature-flags" data-label="depends on">web-shop depends on feature-flags</li>
                <li data-edge data-source="mobile-web" data-target="checkout-ui" data-label="depends on">mobile-web depends on checkout-ui</li>
                <li data-edge data-source="mobile-web" data-target="catalogue-ui" data-label="depends on">mobile-web depends on catalogue-ui</li>
                <li data-edge data-source="mobile-web" data-target="ui-kit" data-label="depends on">mobile-web depends on ui-kit</li>
                <li data-edge data-source="admin-console" data-target="ui-kit" data-label="depends on">admin-console depends on ui-kit</li>
                <li data-edge data-source="admin-console" data-target="form-kit" data-label="depends on">admin-console depends on form-kit</li>
                <li data-edge data-source="admin-console" data-target="charts" data-label="depends on">admin-console depends on charts</li>
                <li data-edge data-source="admin-console" data-target="auth-client" data-label="depends on">admin-console depends on auth-client</li>
                <li data-edge data-source="checkout-ui" data-target="ui-kit" data-label="depends on">checkout-ui depends on ui-kit</li>
                <li data-edge data-source="checkout-ui" data-target="form-kit" data-label="depends on">checkout-ui depends on form-kit</li>
                <li data-edge data-source="checkout-ui" data-target="http-client" data-label="depends on">checkout-ui depends on http-client</li>
                <li data-edge data-source="checkout-ui" data-target="money" data-label="depends on">checkout-ui depends on money</li>
                <li data-edge data-source="checkout-ui" data-target="analytics" data-label="depends on">checkout-ui depends on analytics</li>
                <li data-edge data-source="catalogue-ui" data-target="ui-kit" data-label="depends on">catalogue-ui depends on ui-kit</li>
                <li data-edge data-source="catalogue-ui" data-target="http-client" data-label="depends on">catalogue-ui depends on http-client</li>
                <li data-edge data-source="catalogue-ui" data-target="money" data-label="depends on">catalogue-ui depends on money</li>
                <li data-edge data-source="account-ui" data-target="form-kit" data-label="depends on">account-ui depends on form-kit</li>
                <li data-edge data-source="account-ui" data-target="auth-client" data-label="depends on">account-ui depends on auth-client</li>
                <li data-edge data-source="ui-kit" data-target="design-tokens" data-label="depends on">ui-kit depends on design-tokens</li>
                <li data-edge data-source="ui-kit" data-target="i18n" data-label="depends on">ui-kit depends on i18n</li>
                <li data-edge data-source="form-kit" data-target="ui-kit" data-label="depends on">form-kit depends on ui-kit</li>
                <li data-edge data-source="form-kit" data-target="date-utils" data-label="depends on">form-kit depends on date-utils</li>
                <li data-edge data-source="charts" data-target="design-tokens" data-label="depends on">charts depends on design-tokens</li>
                <li data-edge data-source="charts" data-target="date-utils" data-label="depends on">charts depends on date-utils</li>
                <li data-edge data-source="http-client" data-target="api-schema" data-label="depends on">http-client depends on api-schema</li>
                <li data-edge data-source="http-client" data-target="auth-client" data-label="depends on">http-client depends on auth-client</li>
                <li data-edge data-source="http-client" data-target="logging" data-label="depends on">http-client depends on logging</li>
                <li data-edge data-source="auth-client" data-target="config" data-label="depends on">auth-client depends on config</li>
                <li data-edge data-source="auth-client" data-target="logging" data-label="depends on">auth-client depends on logging</li>
                <li data-edge data-source="analytics" data-target="config" data-label="depends on">analytics depends on config</li>
                <li data-edge data-source="feature-flags" data-target="http-client" data-label="depends on">feature-flags depends on http-client</li>
                <li data-edge data-source="feature-flags" data-target="config" data-label="depends on">feature-flags depends on config</li>
                <li data-edge data-source="money" data-target="i18n" data-label="depends on">money depends on i18n</li>
                <li data-edge data-source="date-utils" data-target="i18n" data-label="depends on">date-utils depends on i18n</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="fit" aria-label="Fit" data-tip="Fit"><i class="ri-focus-3-line"></i></button>
            </div>
            <div class="menu graph-menu" data-graph-menu role="menu" hidden>
                <span class="menu-label" data-graph-field="label"></span>
                <button class="menu-item" role="menuitem" type="button" data-graph-action="focus"><i class="ri-focus-2-line"></i> <span>Show what it touches</span></button>
                <button class="menu-item" role="menuitem" type="button" data-graph-action="unfocus"><i class="ri-fullscreen-exit-line"></i> <span>Show every package</span></button>
            </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--square series-2"></span> <span>Application</span></span>
        <span class="graph-key"><span class="graph-swatch series-3"></span> <span>Feature package</span></span>
        <span class="graph-key"><span class="graph-swatch graph-swatch--diamond series-5"></span> <span>Shared interface</span></span>
        <span class="graph-key"><span class="graph-swatch graph-swatch--hexagon series-6"></span> <span>Library</span></span>
        <span class="graph-key"><span class="graph-line graph-line--arrow"></span> <span>Depends on</span></span>
    </div>
</div>

The side panel

Live — select a service. The panel is what the canvas cannot be: text, and controls a keyboard and a screen reader reach. [data-graph-detail] is shown while a record is selected and hidden while none is. Every [data-graph-field] in it takes that field of the record as text — a data-* of the record's own names one, as team, owner and host do here — and [data-graph-icon] takes its icon. A field the record does not have is hidden. A label written beside it goes with it when the row says which field it belongs to: data-graph-if="contract" on the row hides the whole Contract line for a service that has none, and shows it for the two outside services.

The neighbours are rows cloned from <template data-graph-neighbour> into [data-graph-neighbours], one per linked record. [data-graph-link] takes the label of the link, and the row gets data-graph-direction, which shows one of .graph-direction-out, .graph-direction-in and .graph-direction-both. A data-graph-action in a row acts on that neighbour: select moves the panel there and focus shows its neighbourhood.

.graph-detail on a .card.card--fill in a .sedna-split-aside is as tall as the graph, in full screen as well: its head and foot stay and its body scrolls. --graph-height on the .graph-frame sizes the graph and the panel together. From the keyboard, the arrows move between records and Space selects, and the [data-graph-live] region says each in the sentence of data-graph-announce or data-graph-announce-select.

  • Storefront
  • Checkout API
  • Payments API
  • Orders API
  • Shipping API
  • Catalogue API
  • Search API
  • Notifications
  • Orders database
  • Catalogue database
  • Order events
  • Card processor
  • Carrier
  • The storefront calls the checkout API
  • The storefront calls the catalogue API
  • The storefront calls the search API
  • The checkout API charges through the payments API
  • The checkout API places orders
  • The checkout API asks shipping for a quote
  • The checkout API reserves stock in the catalogue
  • The payments API authorises cards with the card processor
  • The card processor confirms payments back
  • The orders API writes the orders database
  • The orders API publishes order events
  • Order events feed notifications
  • Order events feed the shipping API
  • The shipping API books parcels with the carrier
  • The catalogue API reads the catalogue database
  • The search API indexes the catalogue database

<div class="graph-frame" data-graph-frame>
    <div class="sedna-split">
        <div class="card">
            <div class="graph" data-graph id="xp-panel" data-graph-arrows="target" aria-label="Services, who runs them and where">
                <ul class="graph-data" data-graph-data>
                    <li data-node="storefront" data-kind="Service" data-team="Web" data-owner="Priya Nair" data-host="web-front-01" data-tone="2" data-icon="ri-store-2-line" data-meta="The shop's pages, rendered on the server">Storefront</li>
                    <li data-node="checkout" data-kind="Service" data-team="Checkout" data-owner="Alex Fischer" data-host="checkout-api-02" data-tone="2" data-icon="ri-shopping-cart-2-line" data-meta="Turns a basket into an order" data-hub>Checkout API</li>
                    <li data-node="payments" data-kind="Service" data-team="Checkout" data-owner="Alex Fischer" data-host="payments-api-01" data-tone="2" data-icon="ri-bank-card-line" data-meta="Charges and refunds">Payments API</li>
                    <li data-node="orders" data-kind="Service" data-team="Fulfilment" data-owner="Jordan Weiss" data-host="orders-console-01" data-tone="2" data-icon="ri-file-list-3-line" data-meta="Every order from placed to delivered" data-hub>Orders API</li>
                    <li data-node="shipping" data-kind="Service" data-team="Fulfilment" data-owner="Jordan Weiss" data-host="shipping-api-01" data-tone="2" data-icon="ri-truck-line" data-meta="Quotes, labels and tracking">Shipping API</li>
                    <li data-node="catalogue" data-kind="Service" data-team="Catalogue" data-owner="Rin Nakamura" data-host="catalogue-api-03" data-tone="2" data-icon="ri-book-2-line" data-meta="Products, prices and stock levels">Catalogue API</li>
                    <li data-node="search" data-kind="Service" data-team="Catalogue" data-owner="Rin Nakamura" data-host="search-node-02" data-tone="2" data-icon="ri-search-line" data-meta="Full-text search over the catalogue">Search API</li>
                    <li data-node="notify" data-kind="Service" data-team="Platform" data-owner="Priya Nair" data-host="notify-worker-02" data-tone="2" data-icon="ri-notification-3-line" data-meta="E-mail and push messages">Notifications</li>
                    <li data-node="orders-db" data-kind="Database" data-team="Fulfilment" data-owner="Jordan Weiss" data-host="src-db-14" data-tone="4" data-shape="square" data-icon="ri-database-2-line" data-meta="Orders, lines and their history">Orders database</li>
                    <li data-node="catalogue-db" data-kind="Database" data-team="Catalogue" data-owner="Rin Nakamura" data-host="src-db-11" data-tone="4" data-shape="square" data-icon="ri-database-2-line" data-meta="Products and prices">Catalogue database</li>
                    <li data-node="events" data-kind="Queue" data-team="Platform" data-owner="Priya Nair" data-host="bus-broker-01" data-tone="5" data-shape="diamond" data-icon="ri-stack-line" data-meta="Everything that happens to an order">Order events</li>
                    <li data-node="card-processor" data-kind="Outside service" data-team="Checkout" data-owner="Alex Fischer" data-host="api.example.net" data-contract="Renewed every March" data-tone="6" data-shape="hexagon" data-icon="ri-global-line" data-meta="Authorises card payments">Card processor</li>
                    <li data-node="carrier" data-kind="Outside service" data-team="Fulfilment" data-owner="Jordan Weiss" data-host="tracking.example.org" data-contract="Rolling, 90 days notice" data-tone="6" data-shape="hexagon" data-icon="ri-global-line" data-meta="Parcel labels and tracking">Carrier</li>

                    <li data-edge data-source="storefront" data-target="checkout" data-label="calls">The storefront calls the checkout API</li>
                    <li data-edge data-source="storefront" data-target="catalogue" data-label="calls">The storefront calls the catalogue API</li>
                    <li data-edge data-source="storefront" data-target="search" data-label="calls">The storefront calls the search API</li>
                    <li data-edge data-source="checkout" data-target="payments" data-label="charges">The checkout API charges through the payments API</li>
                    <li data-edge data-source="checkout" data-target="orders" data-label="places">The checkout API places orders</li>
                    <li data-edge data-source="checkout" data-target="shipping" data-label="quotes">The checkout API asks shipping for a quote</li>
                    <li data-edge data-source="checkout" data-target="catalogue" data-label="reserves">The checkout API reserves stock in the catalogue</li>
                    <li data-edge data-source="payments" data-target="card-processor" data-label="authorises">The payments API authorises cards with the card processor</li>
                    <li data-edge data-source="card-processor" data-target="payments" data-label="confirms">The card processor confirms payments back</li>
                    <li data-edge data-source="orders" data-target="orders-db" data-label="writes">The orders API writes the orders database</li>
                    <li data-edge data-source="orders" data-target="events" data-label="publishes">The orders API publishes order events</li>
                    <li data-edge data-source="events" data-target="notify" data-label="feeds">Order events feed notifications</li>
                    <li data-edge data-source="events" data-target="shipping" data-label="feeds">Order events feed the shipping API</li>
                    <li data-edge data-source="shipping" data-target="carrier" data-label="books">The shipping API books parcels with the carrier</li>
                    <li data-edge data-source="catalogue" data-target="catalogue-db" data-label="reads">The catalogue API reads the catalogue database</li>
                    <li data-edge data-source="search" data-target="catalogue-db" data-label="indexes">The search API indexes the catalogue database</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="fit" aria-label="Fit" data-tip="Fit"><i class="ri-focus-3-line"></i></button>
                </div>
                <div class="graph-wait skeleton skeleton-block"></div>
                <p class="visually-hidden" data-graph-live aria-live="polite"
                   data-graph-announce="{label}, {kind}, run by {team}. {links} links."
                   data-graph-announce-select="{label} selected. Its details are beside the graph."></p>
            </div>
        </div>
        <aside class="card card--fill sedna-split-aside graph-detail" data-graph-detail hidden aria-label="The selected service">
            <div class="card-head">
                <span class="sedna-row sedna-gap-2"><i data-graph-icon aria-hidden="true"></i><strong data-graph-field="label"></strong></span>
                <button class="btn btn--ghost btn--sm btn--icon" type="button" data-graph-action="clear" aria-label="Close" data-tip="Close"><i class="ri-close-line"></i></button>
            </div>
            <div class="card-body">
                <p class="text-muted text-sm sedna-mt-0" data-graph-field="meta"></p>
                <div class="kv"><span class="k">Kind</span><span data-graph-field="kind"></span></div>
                <div class="kv"><span class="k">Team</span><span data-graph-field="team"></span></div>
                <div class="kv"><span class="k">Owner</span><span data-graph-field="owner"></span></div>
                <div class="kv"><span class="k">Host</span><span class="v" data-graph-field="host"></span></div>
                <div class="kv" data-graph-if="contract"><span class="k">Contract</span><span data-graph-field="contract"></span></div>
                <div class="card-section">
                    <p class="text-muted text-sm sedna-mt-0 sedna-mb-2">Linked services</p>
                    <ul class="list list--cards" data-graph-neighbours aria-label="Linked services">
                        <template data-graph-neighbour>
                            <li>
                                <div class="list-row">
                                    <span class="graph-direction">
                                        <i class="ri-arrow-right-line graph-direction-out" role="img" aria-label="Outgoing"></i>
                                        <i class="ri-arrow-left-line graph-direction-in" role="img" aria-label="Incoming"></i>
                                        <i class="ri-arrow-left-right-line graph-direction-both" role="img" aria-label="Both ways"></i>
                                    </span>
                                    <span class="list-main">
                                        <button class="btn-bare list-title" type="button" data-graph-action="select" data-graph-field="label"></button>
                                        <span class="list-sub" data-graph-link></span>
                                    </span>
                                    <button class="btn btn--ghost btn--sm btn--icon" type="button" data-graph-action="focus" data-tip="Show its neighbourhood">
                                        <i class="ri-focus-2-line" aria-hidden="true"></i>
                                        <span class="visually-hidden">Show the neighbourhood of <span data-graph-field="label"></span></span>
                                    </button>
                                </div>
                            </li>
                        </template>
                    </ul>
                </div>
            </div>
            <div class="card-foot card-foot--split">
                <button class="btn btn--sm" type="button" data-graph-action="focus"><i class="ri-focus-2-line"></i> <span>Its neighbourhood</span></button>
                <button class="btn btn--ghost btn--sm" type="button" data-graph-action="unfocus">Show all</button>
            </div>
        </aside>
    </div>
</div>

The default tooltip

Live — point at a step. With nothing written, pointing at a record, or reaching it with the arrow keys, shows the library's hover hint — the bubble every data-tip uses — above it, with the record's name and its data-meta. A link says its label. That is enough for most graphs: write a tooltip of your own when a record has more to say.

  • Contract signed
  • Laptop issued
  • Account created
  • Building badge
  • Security training
  • Repository access
  • Meet a buddy
  • First change shipped
  • The laptop is issued once the contract is signed
  • The account is created once the contract is signed
  • The badge is printed once the contract is signed
  • Security training follows the account
  • Security training unlocks repository access
  • Repository access needs the laptop
  • The first change needs repository access
  • The buddy helps ship the first change
<div class="card">
    <div class="graph graph--sm" data-graph id="xp-bubble" data-graph-layout="dagre" data-graph-direction="LR" data-graph-arrows="target" aria-label="A new starter's first week, and what each step waits for">
        <ul class="graph-data" data-graph-data>
            <li data-node="contract" data-tone="3" data-shape="star" data-meta="Before day one · People team" data-root>Contract signed</li>
            <li data-node="laptop" data-tone="2" data-icon="ri-macbook-line" data-meta="Day one · IT desk">Laptop issued</li>
            <li data-node="account" data-tone="2" data-icon="ri-user-add-line" data-meta="Day one · IT desk">Account created</li>
            <li data-node="badge" data-tone="2" data-icon="ri-bank-card-2-line" data-meta="Day one · Reception">Building badge</li>
            <li data-node="security" data-tone="5" data-icon="ri-shield-check-line" data-meta="Day two · 45 minutes online">Security training</li>
            <li data-node="repos" data-tone="4" data-icon="ri-git-repository-line" data-meta="Day two · Team lead">Repository access</li>
            <li data-node="buddy" data-tone="5" data-icon="ri-team-line" data-meta="Day three · Team lead">Meet a buddy</li>
            <li data-node="first-change" data-tone="4" data-icon="ri-rocket-2-line" data-meta="By Friday · with the buddy">First change shipped</li>

            <li data-edge data-source="contract" data-target="laptop" data-label="then">The laptop is issued once the contract is signed</li>
            <li data-edge data-source="contract" data-target="account" data-label="then">The account is created once the contract is signed</li>
            <li data-edge data-source="contract" data-target="badge" data-label="then">The badge is printed once the contract is signed</li>
            <li data-edge data-source="account" data-target="security" data-label="then">Security training follows the account</li>
            <li data-edge data-source="security" data-target="repos" data-label="unlocks">Security training unlocks repository access</li>
            <li data-edge data-source="laptop" data-target="repos" data-label="then">Repository access needs the laptop</li>
            <li data-edge data-source="repos" data-target="first-change" data-label="then">The first change needs repository access</li>
            <li data-edge data-source="buddy" data-target="first-change" data-label="helps">The buddy helps ship the first change</li>
        </ul>
        <div class="graph-canvas"></div>
        <div class="graph-wait skeleton skeleton-block"></div>
    </div>
</div>

A tooltip of your own

Live — point at a device, then at a link. .graph-tip with data-graph-tip, written once inside the graph and filled for each record: .graph-tip-head holds the [data-graph-icon] and the .graph-tip-title, .graph-tip-meta is a quieter line, and .graph-tip-ref is an identifier — an address, a host name — in the monospace face. Any field fills a slot, and one a record lacks is hidden: the upstream provider has no rack.

data-graph-tip="edge" is the tooltip for links, here with the link's label and its data-speed. The script fills both with text and places them inside the frame, clear of the pointer; the words, the order and the look are the app's.

  • Upstream provider
  • edge-router-01
  • edge-router-02
  • firewall-01
  • core-switch-01
  • core-switch-02
  • access-switch-01
  • access-switch-02
  • access-switch-03
  • wifi-controller-01
  • print-server-01
  • build-runner-04
  • src-db-14
  • orders-console-01
  • backup-store-02
  • The upstream provider connects to edge-router-01
  • The upstream provider connects to edge-router-02, on standby
  • edge-router-01 connects to firewall-01
  • edge-router-02 connects to firewall-01, on standby
  • firewall-01 connects to core-switch-01
  • firewall-01 connects to core-switch-02
  • core-switch-01 connects to access-switch-01
  • core-switch-01 connects to access-switch-02
  • core-switch-02 connects to access-switch-03
  • access-switch-01 connects wifi-controller-01
  • access-switch-02 connects print-server-01
  • access-switch-03 connects build-runner-04
  • access-switch-03 connects src-db-14
  • access-switch-03 connects orders-console-01
  • access-switch-03 connects backup-store-02
<div class="card">
    <div class="graph" data-graph id="xp-tip" data-graph-layout="dagre" data-graph-direction="LR" data-graph-nodes="box" aria-label="The office network, from the upstream provider down to the hosts">
        <ul class="graph-data" data-graph-data>
            <li data-node="provider" data-tone="muted" data-shape="hexagon" data-icon="ri-cloud-line" data-meta="Upstream provider" data-address="198.51.100.1" data-root>Upstream provider</li>
            <li data-node="edge-router-01" data-tone="2" data-shape="diamond" data-icon="ri-router-line" data-meta="Edge router, active" data-address="203.0.113.1" data-rack="Rack A1, room 2">edge-router-01</li>
            <li data-node="edge-router-02" data-tone="2" data-shape="diamond" data-icon="ri-router-line" data-meta="Edge router, standby" data-address="203.0.113.2" data-rack="Rack A2, room 2">edge-router-02</li>
            <li data-node="firewall-01" data-tone="3" data-shape="square" data-icon="ri-shield-keyhole-line" data-meta="Firewall pair" data-address="192.0.2.1" data-rack="Rack A1, room 2" data-hub>firewall-01</li>
            <li data-node="core-switch-01" data-tone="5" data-shape="square" data-icon="ri-git-merge-line" data-meta="Core switch" data-address="192.0.2.10" data-rack="Rack B1, room 2" data-hub>core-switch-01</li>
            <li data-node="core-switch-02" data-tone="5" data-shape="square" data-icon="ri-git-merge-line" data-meta="Core switch" data-address="192.0.2.11" data-rack="Rack B2, room 2">core-switch-02</li>
            <li data-node="access-switch-01" data-tone="4" data-icon="ri-hard-drive-2-line" data-meta="Access switch, floor 1" data-address="192.0.2.21" data-rack="Floor 1 cabinet">access-switch-01</li>
            <li data-node="access-switch-02" data-tone="4" data-icon="ri-hard-drive-2-line" data-meta="Access switch, floor 2" data-address="192.0.2.22" data-rack="Floor 2 cabinet">access-switch-02</li>
            <li data-node="access-switch-03" data-tone="4" data-icon="ri-hard-drive-2-line" data-meta="Access switch, server room" data-address="192.0.2.23" data-rack="Rack C1, room 2">access-switch-03</li>
            <li data-node="wifi-controller-01" data-tone="6" data-icon="ri-wifi-line" data-meta="Wireless controller" data-address="192.0.2.40" data-rack="Floor 1 cabinet">wifi-controller-01</li>
            <li data-node="print-server-01" data-tone="6" data-icon="ri-printer-line" data-meta="Print server" data-address="192.0.2.60" data-rack="Floor 2 cabinet">print-server-01</li>
            <li data-node="build-runner-04" data-tone="6" data-icon="ri-server-line" data-meta="Build runner" data-address="192.0.2.104" data-rack="Rack C1, room 2">build-runner-04</li>
            <li data-node="src-db-14" data-tone="6" data-icon="ri-database-2-line" data-meta="Source database" data-address="192.0.2.114" data-rack="Rack C2, room 2">src-db-14</li>
            <li data-node="orders-console-01" data-tone="6" data-icon="ri-computer-line" data-meta="Orders console" data-address="192.0.2.121" data-rack="Rack C2, room 2">orders-console-01</li>
            <li data-node="backup-store-02" data-tone="6" data-icon="ri-archive-drawer-line" data-meta="Backup store" data-address="192.0.2.132" data-rack="Rack C3, room 2">backup-store-02</li>

            <li data-edge data-source="provider" data-target="edge-router-01" data-label="transit" data-speed="10 Gbit/s">The upstream provider connects to edge-router-01</li>
            <li data-edge data-source="provider" data-target="edge-router-02" data-label="transit" data-speed="10 Gbit/s" data-line="dashed">The upstream provider connects to edge-router-02, on standby</li>
            <li data-edge data-source="edge-router-01" data-target="firewall-01" data-label="uplink" data-speed="10 Gbit/s">edge-router-01 connects to firewall-01</li>
            <li data-edge data-source="edge-router-02" data-target="firewall-01" data-label="uplink" data-speed="10 Gbit/s" data-line="dashed">edge-router-02 connects to firewall-01, on standby</li>
            <li data-edge data-source="firewall-01" data-target="core-switch-01" data-label="trunk" data-speed="2 × 10 Gbit/s" data-weight="heavy">firewall-01 connects to core-switch-01</li>
            <li data-edge data-source="firewall-01" data-target="core-switch-02" data-label="trunk" data-speed="2 × 10 Gbit/s" data-weight="heavy">firewall-01 connects to core-switch-02</li>
            <li data-edge data-source="core-switch-01" data-target="access-switch-01" data-label="uplink" data-speed="1 Gbit/s">core-switch-01 connects to access-switch-01</li>
            <li data-edge data-source="core-switch-01" data-target="access-switch-02" data-label="uplink" data-speed="1 Gbit/s">core-switch-01 connects to access-switch-02</li>
            <li data-edge data-source="core-switch-02" data-target="access-switch-03" data-label="uplink" data-speed="10 Gbit/s">core-switch-02 connects to access-switch-03</li>
            <li data-edge data-source="access-switch-01" data-target="wifi-controller-01" data-label="access" data-speed="1 Gbit/s">access-switch-01 connects wifi-controller-01</li>
            <li data-edge data-source="access-switch-02" data-target="print-server-01" data-label="access" data-speed="1 Gbit/s">access-switch-02 connects print-server-01</li>
            <li data-edge data-source="access-switch-03" data-target="build-runner-04" data-label="access" data-speed="10 Gbit/s">access-switch-03 connects build-runner-04</li>
            <li data-edge data-source="access-switch-03" data-target="src-db-14" data-label="access" data-speed="10 Gbit/s">access-switch-03 connects src-db-14</li>
            <li data-edge data-source="access-switch-03" data-target="orders-console-01" data-label="access" data-speed="10 Gbit/s">access-switch-03 connects orders-console-01</li>
            <li data-edge data-source="access-switch-03" data-target="backup-store-02" data-label="access" data-speed="10 Gbit/s">access-switch-03 connects backup-store-02</li>
        </ul>
        <div class="graph-canvas"></div>
        <div class="graph-tip" data-graph-tip hidden>
            <span class="graph-tip-head"><i data-graph-icon aria-hidden="true"></i><span class="graph-tip-title" data-graph-field="label"></span></span>
            <span class="graph-tip-meta" data-graph-field="meta"></span>
            <span class="graph-tip-ref" data-graph-field="address"></span>
            <span class="graph-tip-meta" data-graph-field="rack"></span>
        </div>
        <div class="graph-tip" data-graph-tip="edge" hidden>
            <span class="graph-tip-title" data-graph-field="label"></span>
            <span class="graph-tip-ref" data-graph-field="speed"></span>
        </div>
        <div class="graph-wait skeleton skeleton-block"></div>
    </div>
</div>

The context menu

Live — right-click a page, or the empty canvas. .menu.graph-menu with data-graph-menu, inside the graph, opens where a right click, a long press or the context-menu key lands. It is the library's .menu with the app's items. A data-graph-action item acts on the record the menu was opened on, and an item that does not apply hides itself: open on a record with no data-href, every record's item on the background. A .menu-label with data-graph-field="label" names the record.

Here open goes to the page, hide takes one record away, and show-all brings back every record hidden that way. The arrow keys move through the items, and Esc closes the menu and puts focus back on the graph. sedna-graph-context is dispatched first and is cancelable, for an app that opens something of its own instead.

Right-click a page, or press Shift + F10 on one.
  • Cards
  • Lists
  • Tabs
  • Buttons
  • Toolbar
  • Menus
  • Popovers
  • Drag and drop
  • Text fields
  • Selects
  • Segmented control
  • Tables
  • Pagination
  • Graph
  • Exploring a graph
  • Graph plugins
  • Chips
  • Badges
  • Modal
  • Drawers and sheets
  • Empty states
  • Skeletons
  • Toolbar builds on Buttons
  • Toolbar builds on Chips
  • Toolbar builds on Selects
  • Toolbar builds on Text fields
  • Toolbar builds on Segmented control
  • Menus build on Buttons
  • Popovers build on Buttons
  • Pagination builds on Buttons
  • Tables build on Toolbar
  • Tables build on Pagination
  • Tables build on Empty states
  • Tables build on Skeletons
  • Tables build on Badges
  • Lists build on Empty states
  • Tabs build on Cards
  • Modal builds on Buttons
  • Drawers build on Buttons
  • Drawers build on Lists
  • Drag and drop builds on Lists
  • Drag and drop builds on Cards
  • Graph builds on Cards
  • Graph builds on Toolbar
  • Graph builds on Skeletons
  • Graph builds on Empty states
  • Exploring a graph builds on Graph
  • Exploring a graph builds on Chips
  • Exploring a graph builds on Menus
  • Exploring a graph builds on Lists
  • Exploring a graph builds on Segmented control
  • Graph plugins build on Graph
  • Graph plugins build on Buttons
Structure Actions Forms Data Labels Overlays Feedback
<div class="graph-frame" data-graph-frame>
    <div class="toolbar">
        <span class="text-sm text-muted">Right-click a page, or press <kbd class="kbd">Shift</kbd> + <kbd class="kbd">F10</kbd> on one.</span>
        <span class="toolbar-count" data-graph-stats="{nodes} of {totalNodes} pages"></span>
    </div>
    <div class="card">
        <div class="graph" data-graph id="xp-menu" data-graph-arrows="target" aria-label="Pages of this catalogue and the pages each builds on">
            <ul class="graph-data" data-graph-data>
                <li data-node="card" data-kind="structure" data-tone="2" data-href="/card" data-meta="Structure">Cards</li>
                <li data-node="list" data-kind="structure" data-tone="2" data-href="/list" data-meta="Structure">Lists</li>
                <li data-node="tabs" data-kind="structure" data-tone="2" data-href="/tabs" data-meta="Structure">Tabs</li>
                <li data-node="button" data-kind="actions" data-tone="3" data-shape="diamond" data-href="/button" data-meta="Actions" data-hub>Buttons</li>
                <li data-node="toolbar" data-kind="actions" data-tone="3" data-shape="diamond" data-href="/toolbar" data-meta="Actions">Toolbar</li>
                <li data-node="menu" data-kind="actions" data-tone="3" data-shape="diamond" data-href="/menu" data-meta="Actions">Menus</li>
                <li data-node="popover" data-kind="actions" data-tone="3" data-shape="diamond" data-href="/popover" data-meta="Actions">Popovers</li>
                <li data-node="drag-drop" data-kind="actions" data-tone="3" data-shape="diamond" data-href="/drag-drop" data-meta="Actions">Drag and drop</li>
                <li data-node="form-text" data-kind="forms" data-tone="4" data-shape="square" data-href="/form-text" data-meta="Forms">Text fields</li>
                <li data-node="form-select" data-kind="forms" data-tone="4" data-shape="square" data-href="/form-select" data-meta="Forms">Selects</li>
                <li data-node="segmented" data-kind="forms" data-tone="4" data-shape="square" data-href="/segmented" data-meta="Forms">Segmented control</li>
                <li data-node="table" data-kind="data" data-tone="5" data-shape="star" data-href="/table" data-meta="Data">Tables</li>
                <li data-node="pager" data-kind="data" data-tone="5" data-shape="star" data-href="/pager" data-meta="Data">Pagination</li>
                <li data-node="graph" data-kind="data" data-tone="5" data-shape="star" data-href="/graph" data-meta="Data" data-hub>Graph</li>
                <li data-node="graph-exploring" data-kind="data" data-tone="5" data-shape="star" data-href="/graph-exploring" data-meta="Data">Exploring a graph</li>
                <li data-node="graph-plugins" data-kind="data" data-tone="5" data-shape="star" data-href="/graph-plugins" data-meta="Data">Graph plugins</li>
                <li data-node="chip" data-kind="labels" data-tone="6" data-shape="tag" data-href="/chip" data-meta="Labels">Chips</li>
                <li data-node="badge" data-kind="labels" data-tone="6" data-shape="tag" data-href="/badge" data-meta="Labels">Badges</li>
                <li data-node="modal" data-kind="overlays" data-tone="1" data-shape="rounded" data-href="/modal" data-meta="Overlays">Modal</li>
                <li data-node="drawer" data-kind="overlays" data-tone="1" data-shape="rounded" data-href="/drawer" data-meta="Overlays">Drawers and sheets</li>
                <li data-node="empty-state" data-kind="feedback" data-tone="info" data-shape="triangle" data-href="/empty-state" data-meta="Feedback">Empty states</li>
                <li data-node="skeleton" data-kind="feedback" data-tone="info" data-shape="triangle" data-href="/skeleton" data-meta="Feedback">Skeletons</li>

                <li data-edge data-source="toolbar" data-target="button" data-label="builds on">Toolbar builds on Buttons</li>
                <li data-edge data-source="toolbar" data-target="chip" data-label="builds on">Toolbar builds on Chips</li>
                <li data-edge data-source="toolbar" data-target="form-select" data-label="builds on">Toolbar builds on Selects</li>
                <li data-edge data-source="toolbar" data-target="form-text" data-label="builds on">Toolbar builds on Text fields</li>
                <li data-edge data-source="toolbar" data-target="segmented" data-label="builds on">Toolbar builds on Segmented control</li>
                <li data-edge data-source="menu" data-target="button" data-label="builds on">Menus build on Buttons</li>
                <li data-edge data-source="popover" data-target="button" data-label="builds on">Popovers build on Buttons</li>
                <li data-edge data-source="pager" data-target="button" data-label="builds on">Pagination builds on Buttons</li>
                <li data-edge data-source="table" data-target="toolbar" data-label="builds on">Tables build on Toolbar</li>
                <li data-edge data-source="table" data-target="pager" data-label="builds on">Tables build on Pagination</li>
                <li data-edge data-source="table" data-target="empty-state" data-label="builds on">Tables build on Empty states</li>
                <li data-edge data-source="table" data-target="skeleton" data-label="builds on">Tables build on Skeletons</li>
                <li data-edge data-source="table" data-target="badge" data-label="builds on">Tables build on Badges</li>
                <li data-edge data-source="list" data-target="empty-state" data-label="builds on">Lists build on Empty states</li>
                <li data-edge data-source="tabs" data-target="card" data-label="builds on">Tabs build on Cards</li>
                <li data-edge data-source="modal" data-target="button" data-label="builds on">Modal builds on Buttons</li>
                <li data-edge data-source="drawer" data-target="button" data-label="builds on">Drawers build on Buttons</li>
                <li data-edge data-source="drawer" data-target="list" data-label="builds on">Drawers build on Lists</li>
                <li data-edge data-source="drag-drop" data-target="list" data-label="builds on">Drag and drop builds on Lists</li>
                <li data-edge data-source="drag-drop" data-target="card" data-label="builds on">Drag and drop builds on Cards</li>
                <li data-edge data-source="graph" data-target="card" data-label="builds on">Graph builds on Cards</li>
                <li data-edge data-source="graph" data-target="toolbar" data-label="builds on">Graph builds on Toolbar</li>
                <li data-edge data-source="graph" data-target="skeleton" data-label="builds on">Graph builds on Skeletons</li>
                <li data-edge data-source="graph" data-target="empty-state" data-label="builds on">Graph builds on Empty states</li>
                <li data-edge data-source="graph-exploring" data-target="graph" data-label="builds on">Exploring a graph builds on Graph</li>
                <li data-edge data-source="graph-exploring" data-target="chip" data-label="builds on">Exploring a graph builds on Chips</li>
                <li data-edge data-source="graph-exploring" data-target="menu" data-label="builds on">Exploring a graph builds on Menus</li>
                <li data-edge data-source="graph-exploring" data-target="list" data-label="builds on">Exploring a graph builds on Lists</li>
                <li data-edge data-source="graph-exploring" data-target="segmented" data-label="builds on">Exploring a graph builds on Segmented control</li>
                <li data-edge data-source="graph-plugins" data-target="graph" data-label="builds on">Graph plugins build on Graph</li>
                <li data-edge data-source="graph-plugins" data-target="button" data-label="builds on">Graph plugins build on Buttons</li>
            </ul>
            <div class="graph-canvas"></div>
            <div class="menu graph-menu" data-graph-menu role="menu" hidden>
                <span class="menu-label" data-graph-field="label"></span>
                <button class="menu-item" role="menuitem" type="button" data-graph-action="open"><i class="ri-external-link-line"></i> <span>Open the page</span></button>
                <button class="menu-item" role="menuitem" type="button" data-graph-action="focus"><i class="ri-focus-2-line"></i> <span>Show its neighbourhood</span></button>
                <button class="menu-item" role="menuitem" type="button" data-graph-action="hide"><i class="ri-eye-off-line"></i> <span>Hide it</span></button>
                <span class="menu-label">The whole map</span>
                <button class="menu-item" role="menuitem" type="button" data-graph-action="unfocus"><i class="ri-fullscreen-exit-line"></i> <span>Show the whole map</span></button>
                <button class="menu-item" role="menuitem" type="button" data-graph-action="show-all"><i class="ri-eye-line"></i> <span>Show hidden pages</span></button>
            </div>
            <div class="graph-wait skeleton skeleton-block"></div>
        </div>
    </div>
    <div class="graph-legend">
        <span class="graph-key"><span class="graph-swatch series-2"></span> <span>Structure</span></span>
        <span class="graph-key"><span class="graph-swatch graph-swatch--diamond series-3"></span> <span>Actions</span></span>
        <span class="graph-key"><span class="graph-swatch graph-swatch--square series-4"></span> <span>Forms</span></span>
        <span class="graph-key"><span class="graph-swatch graph-swatch--star series-5"></span> <span>Data</span></span>
        <span class="graph-key"><span class="graph-swatch graph-swatch--tag series-6"></span> <span>Labels</span></span>
        <span class="graph-key"><span class="graph-swatch graph-swatch--rounded series-1"></span> <span>Overlays</span></span>
        <span class="graph-key"><span class="graph-swatch graph-swatch--triangle series-info"></span> <span>Feedback</span></span>
    </div>
</div>

Colour by

Live. data-tone is the colouring a graph starts in; data-tone-region and data-tone-status on the same records are two more, and a data-graph-option="colour" control switches between them — tone, or the name after data-tone-. Every tone is a .series-* colour, so the keys are the canvas's own in every theme and colour-vision setting.

Each colouring's keys sit beside its radio, so the legend in force is the one that is chosen, with no script to swap legends. The shape stays with the kind whatever the colour says.

  • North hub
  • South hub
  • Hilltop depot
  • Harbour depot
  • Riverside depot
  • Airport depot
  • North Gate store
  • University store
  • Botanic Garden store
  • Old Town store
  • Seafront store
  • Fish Market store
  • Mill Lane store
  • East End store
  • Canal Street store
  • Exhibition store
  • Stadium store
  • South Park store
  • The two hubs share a trunk route
  • North hub supplies Hilltop depot
  • North hub supplies Harbour depot
  • South hub supplies Riverside depot
  • South hub supplies Airport depot
  • Hilltop depot delivers to North Gate store
  • Hilltop depot delivers to University store
  • Hilltop depot delivers to Botanic Garden store
  • Harbour depot delivers to Old Town store
  • Harbour depot delivers to Seafront store
  • Harbour depot delivers to Fish Market store
  • Riverside depot delivers to Mill Lane store
  • Riverside depot delivers to East End store
  • Riverside depot delivers to Canal Street store
  • Airport depot delivers to Exhibition store
  • Airport depot delivers to Stadium store
  • Airport depot delivers to South Park store
  • Riverside depot covers South Park store while the airport depot is stopped
Hub Depot Store
North East South West
On schedule Running late Stopped
<div class="graph-frame" data-graph-frame>
    <div class="card">
        <div class="graph" data-graph id="xp-colour" data-graph-arrows="target" aria-label="Deliveries from the hubs through the depots to the stores">
            <ul class="graph-data" data-graph-data>
                <li data-node="north-hub" data-tone="3" data-tone-region="2" data-tone-status="go" data-shape="star" data-icon="ri-building-4-line" data-meta="Hub · North · on schedule" data-hub>North hub</li>
                <li data-node="south-hub" data-tone="3" data-tone-region="3" data-tone-status="warn" data-shape="star" data-icon="ri-building-4-line" data-meta="Hub · South · running late" data-hub>South hub</li>
                <li data-node="hilltop" data-tone="5" data-tone-region="2" data-tone-status="go" data-shape="rounded" data-meta="Depot · North · on schedule">Hilltop depot</li>
                <li data-node="harbour" data-tone="5" data-tone-region="5" data-tone-status="go" data-shape="rounded" data-meta="Depot · West · on schedule">Harbour depot</li>
                <li data-node="riverside" data-tone="5" data-tone-region="4" data-tone-status="warn" data-shape="rounded" data-meta="Depot · East · running late">Riverside depot</li>
                <li data-node="airport" data-tone="5" data-tone-region="3" data-tone-status="danger" data-shape="rounded" data-meta="Depot · South · stopped">Airport depot</li>
                <li data-node="north-gate" data-tone="2" data-tone-region="2" data-tone-status="go" data-meta="Store · North · on schedule">North Gate store</li>
                <li data-node="university" data-tone="2" data-tone-region="2" data-tone-status="go" data-meta="Store · North · on schedule">University store</li>
                <li data-node="botanic" data-tone="2" data-tone-region="2" data-tone-status="go" data-meta="Store · North · on schedule">Botanic Garden store</li>
                <li data-node="old-town" data-tone="2" data-tone-region="5" data-tone-status="go" data-meta="Store · West · on schedule">Old Town store</li>
                <li data-node="seafront" data-tone="2" data-tone-region="5" data-tone-status="go" data-meta="Store · West · on schedule">Seafront store</li>
                <li data-node="fish-market" data-tone="2" data-tone-region="5" data-tone-status="warn" data-meta="Store · West · running late">Fish Market store</li>
                <li data-node="mill-lane" data-tone="2" data-tone-region="4" data-tone-status="warn" data-meta="Store · East · running late">Mill Lane store</li>
                <li data-node="east-end" data-tone="2" data-tone-region="4" data-tone-status="warn" data-meta="Store · East · running late">East End store</li>
                <li data-node="canal-street" data-tone="2" data-tone-region="4" data-tone-status="go" data-meta="Store · East · on schedule">Canal Street store</li>
                <li data-node="exhibition" data-tone="2" data-tone-region="3" data-tone-status="danger" data-meta="Store · South · stopped">Exhibition store</li>
                <li data-node="stadium" data-tone="2" data-tone-region="3" data-tone-status="danger" data-meta="Store · South · stopped">Stadium store</li>
                <li data-node="south-park" data-tone="2" data-tone-region="3" data-tone-status="warn" data-meta="Store · South · running late">South Park store</li>

                <li data-edge data-source="north-hub" data-target="south-hub" data-label="trunk" data-weight="heavy" data-arrow="both">The two hubs share a trunk route</li>
                <li data-edge data-source="north-hub" data-target="hilltop" data-label="supplies">North hub supplies Hilltop depot</li>
                <li data-edge data-source="north-hub" data-target="harbour" data-label="supplies">North hub supplies Harbour depot</li>
                <li data-edge data-source="south-hub" data-target="riverside" data-label="supplies">South hub supplies Riverside depot</li>
                <li data-edge data-source="south-hub" data-target="airport" data-label="supplies">South hub supplies Airport depot</li>
                <li data-edge data-source="hilltop" data-target="north-gate" data-label="delivers">Hilltop depot delivers to North Gate store</li>
                <li data-edge data-source="hilltop" data-target="university" data-label="delivers">Hilltop depot delivers to University store</li>
                <li data-edge data-source="hilltop" data-target="botanic" data-label="delivers">Hilltop depot delivers to Botanic Garden store</li>
                <li data-edge data-source="harbour" data-target="old-town" data-label="delivers">Harbour 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="harbour" data-target="fish-market" data-label="delivers">Harbour depot delivers to Fish Market store</li>
                <li data-edge data-source="riverside" data-target="mill-lane" data-label="delivers">Riverside depot delivers to Mill Lane store</li>
                <li data-edge data-source="riverside" data-target="east-end" data-label="delivers">Riverside depot delivers to East End store</li>
                <li data-edge data-source="riverside" data-target="canal-street" data-label="delivers">Riverside depot delivers to Canal Street store</li>
                <li data-edge data-source="airport" data-target="exhibition" data-label="delivers">Airport depot delivers to Exhibition store</li>
                <li data-edge data-source="airport" data-target="stadium" data-label="delivers">Airport depot delivers to Stadium store</li>
                <li data-edge data-source="airport" data-target="south-park" data-label="delivers">Airport depot delivers to South Park store</li>
                <li data-edge data-source="riverside" data-target="south-park" data-label="covers" data-line="dashed">Riverside depot covers South Park store while the airport depot is stopped</li>
            </ul>
            <div class="graph-canvas"></div>
            <div class="graph-wait skeleton skeleton-block"></div>
        </div>
    </div>
    <div class="sedna-col sedna-gap-2" role="radiogroup" aria-label="Colour by">
        <div class="sedna-row-wrap">
            <label class="form-check" style="min-width:7rem"><input type="radio" name="xp-colour-by" data-graph-option="colour" value="tone" checked /> <span>Kind</span></label>
            <div class="graph-legend">
                <span class="graph-key"><span class="graph-swatch graph-swatch--star series-3"></span> <span>Hub</span></span>
                <span class="graph-key"><span class="graph-swatch graph-swatch--rounded series-5"></span> <span>Depot</span></span>
                <span class="graph-key"><span class="graph-swatch series-2"></span> <span>Store</span></span>
            </div>
        </div>
        <div class="sedna-row-wrap">
            <label class="form-check" style="min-width:7rem"><input type="radio" name="xp-colour-by" data-graph-option="colour" value="region" /> <span>Region</span></label>
            <div class="graph-legend">
                <span class="graph-key"><span class="graph-swatch series-2"></span> <span>North</span></span>
                <span class="graph-key"><span class="graph-swatch series-4"></span> <span>East</span></span>
                <span class="graph-key"><span class="graph-swatch series-3"></span> <span>South</span></span>
                <span class="graph-key"><span class="graph-swatch series-5"></span> <span>West</span></span>
            </div>
        </div>
        <div class="sedna-row-wrap">
            <label class="form-check" style="min-width:7rem"><input type="radio" name="xp-colour-by" data-graph-option="colour" value="status" /> <span>Today</span></label>
            <div class="graph-legend">
                <span class="graph-key"><span class="graph-swatch series-go"></span> <span>On schedule</span></span>
                <span class="graph-key"><span class="graph-swatch series-warn"></span> <span>Running late</span></span>
                <span class="graph-key"><span class="graph-swatch series-danger"></span> <span>Stopped</span></span>
            </div>
        </div>
    </div>
</div>

Names

Live. data-graph-option="labels": auto names what there is room for, the way a map does — what is pointed at, selected or found first, then data-hub records, then the busiest — and leaves out a name that would run into one already placed. Zooming in makes room, so more appear. all names every record; none only the one pointed at, selected or found.

data-graph-option="edge-labels": hover, the default for dots, names a link while one of its records is lit; always writes every link's name, which suits boxes better than a map of dots; none never does. Both options are also attributes on the graph, data-graph-labels and data-graph-edge-labels.

  • Seafront
  • Harbour
  • Fish Market
  • Old Town
  • Central
  • Museum Quarter
  • Parkway
  • Hilltop
  • North Gate
  • University
  • Botanic Garden
  • Market Square
  • Stadium
  • South Park
  • Airport
  • Exhibition
  • Canal Street
  • Mill Lane
  • Riverside
  • East End
  • Lighthouse
  • Observatory
  • Science Park
  • Racecourse
  • Cargo Terminal
  • Docklands
  • Cathedral
  • Town Hall
  • Library
  • The Harbour line runs from Lighthouse to Seafront
  • The Harbour line runs from Seafront to Harbour
  • The Harbour line runs from Harbour to Fish Market
  • The Harbour line runs from Fish Market to Old Town
  • The Harbour line runs from Old Town to Central
  • The Harbour line runs from Central to Museum Quarter
  • The Harbour line runs from Museum Quarter to Parkway
  • The Harbour line runs from Parkway to Hilltop
  • The Harbour line runs from Hilltop to Observatory
  • The University line runs from Science Park to North Gate
  • The University line runs from North Gate to University
  • The University line runs from University to Botanic Garden
  • The University line runs from Botanic Garden to Central
  • The University line runs from Central to Market Square
  • The University line runs from Market Square to Stadium
  • The University line runs from Stadium to South Park
  • The University line runs from South Park to Racecourse
  • The Airport line runs from Cargo Terminal to Airport
  • The Airport line runs from Airport to Exhibition
  • The Airport line runs from Exhibition to Canal Street
  • The Airport line runs from Canal Street to Market Square
  • The Airport line runs from Market Square to Old Town
  • The Airport line runs from Old Town to Mill Lane
  • The Airport line runs from Mill Lane to Riverside
  • The Airport line runs from Riverside to East End
  • The Airport line runs from East End to Docklands
  • The Tram runs from Old Town to Cathedral
  • The Tram runs from Cathedral to Town Hall
  • The Tram runs from Town Hall to Central
  • The Tram runs from Central to Library
  • The Tram runs from Library to University
  • The Circle line runs from Fish Market to Canal Street
  • The Circle line runs from Canal Street to Stadium
  • The Circle line runs from Stadium to Riverside
  • The Circle line runs from Riverside to Parkway
  • The Circle line runs from Parkway to Botanic Garden
  • The Circle line runs from Botanic Garden to Fish Market
Harbour line University line Airport line Circle line Tram Change here
<div class="graph-frame" data-graph-frame>
    <div class="toolbar">
        <div class="toolbar-filters">
            <div class="segmented segmented--sm" role="group" aria-label="Station names">
                <label class="segmented-option"><input type="radio" name="xp-names" data-graph-option="labels" value="auto" checked /> <span>Where they fit</span></label>
                <label class="segmented-option"><input type="radio" name="xp-names" data-graph-option="labels" value="all" /> <span>Every one</span></label>
                <label class="segmented-option"><input type="radio" name="xp-names" data-graph-option="labels" value="none" /> <span>None</span></label>
            </div>
            <div class="segmented segmented--sm" role="group" aria-label="Line names">
                <label class="segmented-option"><input type="radio" name="xp-line-names" data-graph-option="edge-labels" value="hover" checked /> <span>On hover</span></label>
                <label class="segmented-option"><input type="radio" name="xp-line-names" data-graph-option="edge-labels" value="always" /> <span>Always</span></label>
                <label class="segmented-option"><input type="radio" name="xp-line-names" data-graph-option="edge-labels" value="none" /> <span>Never</span></label>
            </div>
        </div>
    </div>
    <div class="card">
        <div class="graph graph--lg" data-graph id="xp-names" aria-label="The city's rail and tram network">
            <ul class="graph-data" data-graph-data>
                <li data-node="seafront" data-tone="muted" data-meta="Harbour line">Seafront</li>
                <li data-node="harbour" data-tone="muted" data-meta="Harbour line">Harbour</li>
                <li data-node="fish-market" data-tone="1" data-shape="rounded" data-meta="Change for the Harbour line and Circle line" data-hub>Fish Market</li>
                <li data-node="old-town" data-tone="1" data-shape="rounded" data-meta="Change for the Harbour line, Airport line and Tram" data-hub>Old Town</li>
                <li data-node="central" data-tone="1" data-shape="rounded" data-meta="Change for the Harbour line, University line and Tram" data-hub>Central</li>
                <li data-node="museum-quarter" data-tone="muted" data-meta="Harbour line">Museum Quarter</li>
                <li data-node="parkway" data-tone="1" data-shape="rounded" data-meta="Change for the Harbour line and Circle line" data-hub>Parkway</li>
                <li data-node="hilltop" data-tone="muted" data-meta="Harbour line">Hilltop</li>
                <li data-node="north-gate" data-tone="muted" data-meta="University line">North Gate</li>
                <li data-node="university" data-tone="1" data-shape="rounded" data-meta="Change for the University line and Tram" data-hub>University</li>
                <li data-node="botanic-garden" data-tone="1" data-shape="rounded" data-meta="Change for the University line and Circle line" data-hub>Botanic Garden</li>
                <li data-node="market-square" data-tone="1" data-shape="rounded" data-meta="Change for the University line and Airport line" data-hub>Market Square</li>
                <li data-node="stadium" data-tone="1" data-shape="rounded" data-meta="Change for the University line and Circle line" data-hub>Stadium</li>
                <li data-node="south-park" data-tone="muted" data-meta="University line">South Park</li>
                <li data-node="airport" data-tone="muted" data-meta="Airport line">Airport</li>
                <li data-node="exhibition" data-tone="muted" data-meta="Airport line">Exhibition</li>
                <li data-node="canal-street" data-tone="1" data-shape="rounded" data-meta="Change for the Airport line and Circle line" data-hub>Canal Street</li>
                <li data-node="mill-lane" data-tone="muted" data-meta="Airport line">Mill Lane</li>
                <li data-node="riverside" data-tone="1" data-shape="rounded" data-meta="Change for the Airport line and Circle line" data-hub>Riverside</li>
                <li data-node="east-end" data-tone="muted" data-meta="Airport line">East End</li>
                <li data-node="lighthouse" data-tone="muted" data-meta="Harbour line">Lighthouse</li>
                <li data-node="observatory" data-tone="muted" data-meta="Harbour line">Observatory</li>
                <li data-node="science-park" data-tone="muted" data-meta="University line">Science Park</li>
                <li data-node="racecourse" data-tone="muted" data-meta="University line">Racecourse</li>
                <li data-node="cargo-terminal" data-tone="muted" data-meta="Airport line">Cargo Terminal</li>
                <li data-node="docklands" data-tone="muted" data-meta="Airport line">Docklands</li>
                <li data-node="cathedral" data-tone="muted" data-meta="Tram">Cathedral</li>
                <li data-node="town-hall" data-tone="muted" data-meta="Tram">Town Hall</li>
                <li data-node="library" data-tone="muted" data-meta="Tram">Library</li>

                <li data-edge data-source="lighthouse" data-target="seafront" data-kind="harbour" data-tone="2" data-weight="heavy" data-label="Harbour line">The Harbour line runs from Lighthouse to Seafront</li>
                <li data-edge data-source="seafront" data-target="harbour" data-kind="harbour" data-tone="2" data-weight="heavy" data-label="Harbour line">The Harbour line runs from Seafront to Harbour</li>
                <li data-edge data-source="harbour" data-target="fish-market" data-kind="harbour" data-tone="2" data-weight="heavy" data-label="Harbour line">The Harbour line runs from Harbour to Fish Market</li>
                <li data-edge data-source="fish-market" data-target="old-town" data-kind="harbour" data-tone="2" data-weight="heavy" data-label="Harbour line">The Harbour line runs from Fish Market to Old Town</li>
                <li data-edge data-source="old-town" data-target="central" data-kind="harbour" data-tone="2" data-weight="heavy" data-label="Harbour line">The Harbour line runs from Old Town to Central</li>
                <li data-edge data-source="central" data-target="museum-quarter" data-kind="harbour" data-tone="2" data-weight="heavy" data-label="Harbour line">The Harbour line runs from Central to Museum Quarter</li>
                <li data-edge data-source="museum-quarter" data-target="parkway" data-kind="harbour" data-tone="2" data-weight="heavy" data-label="Harbour line">The Harbour line runs from Museum Quarter to Parkway</li>
                <li data-edge data-source="parkway" data-target="hilltop" data-kind="harbour" data-tone="2" data-weight="heavy" data-label="Harbour line">The Harbour line runs from Parkway to Hilltop</li>
                <li data-edge data-source="hilltop" data-target="observatory" data-kind="harbour" data-tone="2" data-weight="heavy" data-label="Harbour line">The Harbour line runs from Hilltop to Observatory</li>
                <li data-edge data-source="science-park" data-target="north-gate" data-kind="university" data-tone="3" data-weight="heavy" data-label="University line">The University line runs from Science Park to North Gate</li>
                <li data-edge data-source="north-gate" data-target="university" data-kind="university" data-tone="3" data-weight="heavy" data-label="University line">The University line runs from North Gate to University</li>
                <li data-edge data-source="university" data-target="botanic-garden" data-kind="university" data-tone="3" data-weight="heavy" data-label="University line">The University line runs from University to Botanic Garden</li>
                <li data-edge data-source="botanic-garden" data-target="central" data-kind="university" data-tone="3" data-weight="heavy" data-label="University line">The University line runs from Botanic Garden to Central</li>
                <li data-edge data-source="central" data-target="market-square" data-kind="university" data-tone="3" data-weight="heavy" data-label="University line">The University line runs from Central to Market Square</li>
                <li data-edge data-source="market-square" data-target="stadium" data-kind="university" data-tone="3" data-weight="heavy" data-label="University line">The University line runs from Market Square to Stadium</li>
                <li data-edge data-source="stadium" data-target="south-park" data-kind="university" data-tone="3" data-weight="heavy" data-label="University line">The University line runs from Stadium to South Park</li>
                <li data-edge data-source="south-park" data-target="racecourse" data-kind="university" data-tone="3" data-weight="heavy" data-label="University line">The University line runs from South Park to Racecourse</li>
                <li data-edge data-source="cargo-terminal" data-target="airport" data-kind="airport" data-tone="4" data-weight="heavy" data-label="Airport line">The Airport line runs from Cargo Terminal to Airport</li>
                <li data-edge data-source="airport" data-target="exhibition" data-kind="airport" data-tone="4" data-weight="heavy" data-label="Airport line">The Airport line runs from Airport to Exhibition</li>
                <li data-edge data-source="exhibition" data-target="canal-street" data-kind="airport" data-tone="4" data-weight="heavy" data-label="Airport line">The Airport line runs from Exhibition to Canal Street</li>
                <li data-edge data-source="canal-street" data-target="market-square" data-kind="airport" data-tone="4" data-weight="heavy" data-label="Airport line">The Airport line runs from Canal Street to Market Square</li>
                <li data-edge data-source="market-square" data-target="old-town" data-kind="airport" data-tone="4" data-weight="heavy" data-label="Airport line">The Airport line runs from Market Square to Old Town</li>
                <li data-edge data-source="old-town" data-target="mill-lane" data-kind="airport" data-tone="4" data-weight="heavy" data-label="Airport line">The Airport line runs from Old Town to Mill Lane</li>
                <li data-edge data-source="mill-lane" data-target="riverside" data-kind="airport" data-tone="4" data-weight="heavy" data-label="Airport line">The Airport line runs from Mill Lane to Riverside</li>
                <li data-edge data-source="riverside" data-target="east-end" data-kind="airport" data-tone="4" data-weight="heavy" data-label="Airport line">The Airport line runs from Riverside to East End</li>
                <li data-edge data-source="east-end" data-target="docklands" data-kind="airport" data-tone="4" data-weight="heavy" data-label="Airport line">The Airport line runs from East End to Docklands</li>
                <li data-edge data-source="old-town" data-target="cathedral" data-kind="tram" data-tone="6" data-weight="heavy" data-label="Tram">The Tram runs from Old Town to Cathedral</li>
                <li data-edge data-source="cathedral" data-target="town-hall" data-kind="tram" data-tone="6" data-weight="heavy" data-label="Tram">The Tram runs from Cathedral to Town Hall</li>
                <li data-edge data-source="town-hall" data-target="central" data-kind="tram" data-tone="6" data-weight="heavy" data-label="Tram">The Tram runs from Town Hall to Central</li>
                <li data-edge data-source="central" data-target="library" data-kind="tram" data-tone="6" data-weight="heavy" data-label="Tram">The Tram runs from Central to Library</li>
                <li data-edge data-source="library" data-target="university" data-kind="tram" data-tone="6" data-weight="heavy" data-label="Tram">The Tram runs from Library to University</li>
                <li data-edge data-source="fish-market" data-target="canal-street" data-kind="circle" data-tone="5" data-weight="heavy" data-label="Circle line">The Circle line runs from Fish Market to Canal Street</li>
                <li data-edge data-source="canal-street" data-target="stadium" data-kind="circle" data-tone="5" data-weight="heavy" data-label="Circle line">The Circle line runs from Canal Street to Stadium</li>
                <li data-edge data-source="stadium" data-target="riverside" data-kind="circle" data-tone="5" data-weight="heavy" data-label="Circle line">The Circle line runs from Stadium to Riverside</li>
                <li data-edge data-source="riverside" data-target="parkway" data-kind="circle" data-tone="5" data-weight="heavy" data-label="Circle line">The Circle line runs from Riverside to Parkway</li>
                <li data-edge data-source="parkway" data-target="botanic-garden" data-kind="circle" data-tone="5" data-weight="heavy" data-label="Circle line">The Circle line runs from Parkway to Botanic Garden</li>
                <li data-edge data-source="botanic-garden" data-target="fish-market" data-kind="circle" data-tone="5" data-weight="heavy" data-label="Circle line">The Circle line runs from Botanic Garden to Fish Market</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"><i class="ri-focus-3-line"></i></button>
            </div>
            <div class="graph-wait skeleton skeleton-block"></div>
        </div>
    </div>
    <div class="graph-legend">
        <span class="graph-key"><span class="graph-line graph-line--heavy series-2"></span> <span>Harbour line</span></span>
        <span class="graph-key"><span class="graph-line graph-line--heavy series-3"></span> <span>University line</span></span>
        <span class="graph-key"><span class="graph-line graph-line--heavy series-4"></span> <span>Airport line</span></span>
        <span class="graph-key"><span class="graph-line graph-line--heavy series-5"></span> <span>Circle line</span></span>
        <span class="graph-key"><span class="graph-line graph-line--heavy series-6"></span> <span>Tram</span></span>
        <span class="graph-key"><span class="graph-swatch graph-swatch--rounded series-1"></span> <span>Change here</span></span>
    </div>
</div>

Opening a record

Live — this one navigates. A double click, or Enter on the record the keyboard is on, opens it: sedna-graph-open is dispatched and, unless it is cancelled, the record's data-href is followed through a real link, so Blazor's router and any other client-side router handle it. data-graph-open="tap" opens on a single click instead, for a graph that is a way around, like this map of the graph pages; none never opens.

  • Graph
  • Graph layouts
  • Exploring a graph
  • Graph plugins
  • Graph gallery
  • Graph in Blazor
  • After Graph, read Graph layouts
  • After Graph, read Exploring a graph
  • After Graph, read Graph plugins
  • After Exploring a graph, see the gallery
  • After Graph plugins, read Graph in Blazor
<div class="card">
    <div class="graph" data-graph id="xp-open" data-graph-open="tap" data-graph-nodes="box" data-graph-layout="tree" data-graph-edge-labels="none"
         aria-label="The graph pages of this catalogue. Choose one to go there.">
        <ul class="graph-data" data-graph-data>
            <li data-node="graph" data-tone="1" data-icon="ri-share-circle-line" data-href="/graph" data-meta="The markup and the frame" data-root>Graph</li>
            <li data-node="layouts" data-tone="2" data-icon="ri-mind-map" data-href="/graph-layouts" data-meta="Springs, islands, hierarchies">Graph layouts</li>
            <li data-node="exploring" data-tone="2" data-icon="ri-compass-3-line" data-href="/graph-exploring" data-meta="Filters, search, the panel">Exploring a graph</li>
            <li data-node="plugins" data-tone="2" data-icon="ri-puzzle-2-line" data-href="/graph-plugins" data-meta="Minimap, drawing, folding">Graph plugins</li>
            <li data-node="gallery" data-tone="3" data-icon="ri-gallery-view-2" data-href="/graph-gallery" data-meta="Whole graphs for real jobs">Graph gallery</li>
            <li data-node="blazor" data-tone="3" data-icon="ri-code-box-line" data-href="/graph-blazor" data-meta="Records from C#">Graph in Blazor</li>

            <li data-edge data-source="graph" data-target="layouts" data-label="then">After Graph, read Graph layouts</li>
            <li data-edge data-source="graph" data-target="exploring" data-label="then">After Graph, read Exploring a graph</li>
            <li data-edge data-source="graph" data-target="plugins" data-label="then">After Graph, read Graph plugins</li>
            <li data-edge data-source="exploring" data-target="gallery" data-label="then">After Exploring a graph, see the gallery</li>
            <li data-edge data-source="plugins" data-target="blazor" data-label="then">After Graph plugins, read Graph in Blazor</li>
        </ul>
        <div class="graph-canvas"></div>
        <div class="graph-wait skeleton skeleton-block"></div>
    </div>
</div>

All of it together

Live. One frame: search, a team select, the kinds as chips, the stats and reset in the toolbar; zoom, fit and full screen on the canvas, with .graph-tools-sep between the groups and .graph-when-off and .graph-when-on for the two icons of the full-screen toggle; a tooltip, a menu and a live region inside the graph; the panel beside it. Full screen takes the whole .graph-frame, so the toolbar and the panel come with it. Not one line of it is script.

  • API gateway
  • Storefront
  • Checkout API
  • Basket
  • Payments API
  • Orders API
  • Shipping API
  • Returns
  • Catalogue API
  • Search API
  • Notifications
  • Orders database
  • Catalogue database
  • Basket store
  • Order events
  • Card processor
  • Carrier
  • Mail relay
  • The gateway routes to the storefront
  • The gateway routes to the checkout API
  • The gateway routes to returns
  • The storefront calls the catalogue API
  • The storefront calls the search API
  • The storefront calls the basket
  • The basket writes the basket store
  • The checkout API reads the basket
  • The checkout API charges through the payments API
  • The checkout API places orders
  • The checkout API asks shipping for a quote
  • The payments API authorises cards with the card processor
  • The orders API writes the orders database
  • The orders API publishes order events
  • Order events feed notifications
  • Order events feed the shipping API
  • Notifications send through the mail relay
  • The shipping API books parcels with the carrier
  • Returns refunds through the orders API
  • Returns books return parcels with the carrier
  • The catalogue API reads the catalogue database
  • The search API indexes the catalogue database
No service matches these filters

<div class="graph-frame" data-graph-frame style="--graph-height: 34rem">
    <div class="toolbar">
        <div class="toolbar-filters">
            <input class="form-input form-input--sm toolbar-input" type="search" data-graph-search placeholder="Find a service or host" aria-label="Find a service or host" />
            <select class="form-select form-select--sm toolbar-input" data-graph-filter="team" aria-label="Team">
                <option value="" selected>Every team</option>
                <option value="Checkout">Checkout</option>
                <option value="Catalogue">Catalogue</option>
                <option value="Fulfilment">Fulfilment</option>
                <option value="Platform">Platform</option>
            </select>
            <div class="chip-set" role="group" aria-label="Kinds shown">
                <button class="chip" type="button" data-graph-filter="kind" value="service" aria-pressed="true"><span class="graph-swatch series-2"></span><span class="chip-label">Services</span></button>
                <button class="chip" type="button" data-graph-filter="kind" value="database" aria-pressed="true"><span class="graph-swatch graph-swatch--square series-4"></span><span class="chip-label">Databases</span></button>
                <button class="chip" type="button" data-graph-filter="kind" value="queue" aria-pressed="true"><span class="graph-swatch graph-swatch--diamond series-5"></span><span class="chip-label">Queues</span></button>
                <button class="chip" type="button" data-graph-filter="kind" value="external" aria-pressed="true"><span class="graph-swatch graph-swatch--hexagon series-6"></span><span class="chip-label">Outside</span></button>
            </div>
        </div>
        <div class="toolbar-actions">
            <span class="toolbar-count" data-graph-stats="{nodes} of {totalNodes} · {edges} links" data-graph-stats-match="{matches} found"></span>
            <button class="btn btn--ghost btn--sm" type="button" data-graph-action="reset"><i class="ri-restart-line"></i> <span>Reset</span></button>
        </div>
    </div>
    <div class="sedna-split">
        <div class="card">
            <div class="graph" data-graph id="xp-explorer" data-graph-arrows="target" aria-label="The web shop's services, who runs them and where">
                <ul class="graph-data" data-graph-data>
                    <li data-node="gateway" data-kind="service" data-team="Platform" data-owner="Priya Nair" data-host="gateway-01" data-tone="2" data-icon="ri-door-open-line" data-meta="Every request from outside comes in here" data-hub>API gateway</li>
                    <li data-node="storefront" data-kind="service" data-team="Catalogue" data-owner="Rin Nakamura" data-host="web-front-01" data-tone="2" data-icon="ri-store-2-line" data-meta="The shop's pages, rendered on the server">Storefront</li>
                    <li data-node="checkout" data-kind="service" data-team="Checkout" data-owner="Alex Fischer" data-host="checkout-api-02" data-tone="2" data-icon="ri-shopping-cart-2-line" data-meta="Turns a basket into an order" data-hub>Checkout API</li>
                    <li data-node="basket" data-kind="service" data-team="Checkout" data-owner="Alex Fischer" data-host="basket-api-01" data-tone="2" data-icon="ri-shopping-basket-line" data-meta="What each shopper is about to buy">Basket</li>
                    <li data-node="payments" data-kind="service" data-team="Checkout" data-owner="Alex Fischer" data-host="payments-api-01" data-tone="2" data-icon="ri-bank-card-line" data-meta="Charges and refunds">Payments API</li>
                    <li data-node="orders" data-kind="service" data-team="Fulfilment" data-owner="Jordan Weiss" data-host="orders-console-01" data-tone="2" data-icon="ri-file-list-3-line" data-meta="Every order from placed to delivered" data-hub>Orders API</li>
                    <li data-node="shipping" data-kind="service" data-team="Fulfilment" data-owner="Jordan Weiss" data-host="shipping-api-01" data-tone="2" data-icon="ri-truck-line" data-meta="Quotes, labels and tracking">Shipping API</li>
                    <li data-node="returns" data-kind="service" data-team="Fulfilment" data-owner="Jordan Weiss" data-host="returns-api-01" data-tone="2" data-icon="ri-arrow-go-back-line" data-meta="Return labels and refunds">Returns</li>
                    <li data-node="catalogue" data-kind="service" data-team="Catalogue" data-owner="Rin Nakamura" data-host="catalogue-api-03" data-tone="2" data-icon="ri-book-2-line" data-meta="Products, prices and stock levels">Catalogue API</li>
                    <li data-node="search" data-kind="service" data-team="Catalogue" data-owner="Rin Nakamura" data-host="search-node-02" data-tone="2" data-icon="ri-search-line" data-meta="Full-text search over the catalogue">Search API</li>
                    <li data-node="notify" data-kind="service" data-team="Platform" data-owner="Priya Nair" data-host="notify-worker-02" data-tone="2" data-icon="ri-notification-3-line" data-meta="E-mail and push messages">Notifications</li>
                    <li data-node="orders-db" data-kind="database" data-team="Fulfilment" data-owner="Jordan Weiss" data-host="src-db-14" data-tone="4" data-shape="square" data-icon="ri-database-2-line" data-meta="Orders, lines and their history">Orders database</li>
                    <li data-node="catalogue-db" data-kind="database" data-team="Catalogue" data-owner="Rin Nakamura" data-host="src-db-11" data-tone="4" data-shape="square" data-icon="ri-database-2-line" data-meta="Products and prices">Catalogue database</li>
                    <li data-node="baskets-db" data-kind="database" data-team="Checkout" data-owner="Alex Fischer" data-host="cache-03" data-tone="4" data-shape="square" data-icon="ri-database-2-line" data-meta="Open baskets, kept for a week">Basket store</li>
                    <li data-node="events" data-kind="queue" data-team="Platform" data-owner="Priya Nair" data-host="bus-broker-01" data-tone="5" data-shape="diamond" data-icon="ri-stack-line" data-meta="Everything that happens to an order">Order events</li>
                    <li data-node="card-processor" data-kind="external" data-team="Checkout" data-owner="Alex Fischer" data-host="api.example.net" data-tone="6" data-shape="hexagon" data-icon="ri-global-line" data-meta="Authorises card payments">Card processor</li>
                    <li data-node="carrier" data-kind="external" data-team="Fulfilment" data-owner="Jordan Weiss" data-host="tracking.example.org" data-tone="6" data-shape="hexagon" data-icon="ri-global-line" data-meta="Parcel labels and tracking">Carrier</li>
                    <li data-node="mail-relay" data-kind="external" data-team="Platform" data-owner="Priya Nair" data-host="mail.example.com" data-tone="6" data-shape="hexagon" data-icon="ri-global-line" data-meta="Sends the shop's e-mail">Mail relay</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="gateway" data-target="checkout" data-label="routes">The gateway routes to the checkout API</li>
                    <li data-edge data-source="gateway" data-target="returns" data-label="routes">The gateway routes to returns</li>
                    <li data-edge data-source="storefront" data-target="catalogue" data-label="calls">The storefront calls the catalogue API</li>
                    <li data-edge data-source="storefront" data-target="search" data-label="calls">The storefront calls the search API</li>
                    <li data-edge data-source="storefront" data-target="basket" data-label="calls">The storefront calls the basket</li>
                    <li data-edge data-source="basket" data-target="baskets-db" data-label="writes">The basket writes the basket store</li>
                    <li data-edge data-source="checkout" data-target="basket" data-label="reads">The checkout API reads the basket</li>
                    <li data-edge data-source="checkout" data-target="payments" data-label="charges">The checkout API charges through the payments API</li>
                    <li data-edge data-source="checkout" data-target="orders" data-label="places">The checkout API places orders</li>
                    <li data-edge data-source="checkout" data-target="shipping" data-label="quotes">The checkout API asks shipping for a quote</li>
                    <li data-edge data-source="payments" data-target="card-processor" data-label="authorises">The payments API authorises cards with the card processor</li>
                    <li data-edge data-source="orders" data-target="orders-db" data-label="writes">The orders API writes the orders database</li>
                    <li data-edge data-source="orders" data-target="events" data-label="publishes">The orders API publishes order events</li>
                    <li data-edge data-source="events" data-target="notify" data-label="feeds">Order events feed notifications</li>
                    <li data-edge data-source="events" data-target="shipping" data-label="feeds">Order events feed the shipping API</li>
                    <li data-edge data-source="notify" data-target="mail-relay" data-label="sends">Notifications send through the mail relay</li>
                    <li data-edge data-source="shipping" data-target="carrier" data-label="books">The shipping API books parcels with the carrier</li>
                    <li data-edge data-source="returns" data-target="orders" data-label="refunds">Returns refunds through the orders API</li>
                    <li data-edge data-source="returns" data-target="carrier" data-label="books">Returns books return parcels with the carrier</li>
                    <li data-edge data-source="catalogue" data-target="catalogue-db" data-label="reads">The catalogue API reads the catalogue database</li>
                    <li data-edge data-source="search" data-target="catalogue-db" data-label="indexes">The search API indexes the catalogue database</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"><i class="ri-focus-3-line"></i></button>
                    <span class="graph-tools-sep"></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>
                <div class="graph-tip" data-graph-tip hidden>
                    <span class="graph-tip-head"><i data-graph-icon aria-hidden="true"></i><span class="graph-tip-title" data-graph-field="label"></span></span>
                    <span class="graph-tip-meta" data-graph-field="meta"></span>
                    <span class="graph-tip-ref" data-graph-field="host"></span>
                </div>
                <div class="menu graph-menu" data-graph-menu role="menu" hidden>
                    <span class="menu-label" data-graph-field="label"></span>
                    <button class="menu-item" role="menuitem" type="button" data-graph-action="select"><i class="ri-side-bar-line"></i> <span>Show its details</span></button>
                    <button class="menu-item" role="menuitem" type="button" data-graph-action="focus"><i class="ri-focus-2-line"></i> <span>Show its neighbourhood</span></button>
                    <button class="menu-item" role="menuitem" type="button" data-graph-action="hide"><i class="ri-eye-off-line"></i> <span>Hide it</span></button>
                    <hr class="menu-sep" />
                    <button class="menu-item" role="menuitem" type="button" data-graph-action="unfocus"><i class="ri-fullscreen-exit-line"></i> <span>Show every service</span></button>
                    <button class="menu-item" role="menuitem" type="button" data-graph-action="show-all"><i class="ri-eye-line"></i> <span>Show hidden services</span></button>
                </div>
                <div class="graph-wait skeleton skeleton-block"></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">No service matches these filters</span>
                    <button class="btn btn--sm" type="button" data-graph-action="reset"><i class="ri-filter-off-line"></i> <span>Clear the filters</span></button>
                </div>
                <p class="visually-hidden" data-graph-live aria-live="polite"
                   data-graph-announce="{label}, run by {team}. {links} links."
                   data-graph-announce-select="{label} selected."></p>
            </div>
        </div>
        <aside class="card card--fill sedna-split-aside graph-detail" data-graph-detail hidden aria-label="The selected service">
            <div class="card-head">
                <span class="sedna-row sedna-gap-2"><i data-graph-icon aria-hidden="true"></i><strong data-graph-field="label"></strong></span>
                <button class="btn btn--ghost btn--sm btn--icon" type="button" data-graph-action="clear" aria-label="Close" data-tip="Close"><i class="ri-close-line"></i></button>
            </div>
            <div class="card-body">
                <p class="text-muted text-sm sedna-mt-0" data-graph-field="meta"></p>
                <div class="kv"><span class="k">Team</span><span data-graph-field="team"></span></div>
                <div class="kv"><span class="k">Owner</span><span data-graph-field="owner"></span></div>
                <div class="kv"><span class="k">Host</span><span class="v" data-graph-field="host"></span></div>
                <div class="card-section">
                    <p class="text-muted text-sm sedna-mt-0 sedna-mb-2">Linked services</p>
                    <ul class="list list--cards" data-graph-neighbours aria-label="Linked services">
                        <template data-graph-neighbour>
                            <li>
                                <div class="list-row">
                                    <span class="graph-direction">
                                        <i class="ri-arrow-right-line graph-direction-out" role="img" aria-label="Outgoing"></i>
                                        <i class="ri-arrow-left-line graph-direction-in" role="img" aria-label="Incoming"></i>
                                        <i class="ri-arrow-left-right-line graph-direction-both" role="img" aria-label="Both ways"></i>
                                    </span>
                                    <span class="list-main">
                                        <button class="btn-bare list-title" type="button" data-graph-action="select" data-graph-field="label"></button>
                                        <span class="list-sub" data-graph-link></span>
                                    </span>
                                </div>
                            </li>
                        </template>
                    </ul>
                </div>
            </div>
            <div class="card-foot card-foot--split">
                <button class="btn btn--sm" type="button" data-graph-action="focus"><i class="ri-focus-2-line"></i> <span>Its neighbourhood</span></button>
                <button class="btn btn--ghost btn--sm" type="button" data-graph-action="unfocus">Show all</button>
            </div>
        </aside>
    </div>
</div>

Controls

AttributeOnDoes
data-graph-filter="field"a chipA checkbox, or a button with aria-pressed. Hides records with its value while it is off.
data-graph-filter="field"a select, radiosChooses: only records with the value stay. "" chooses all.
data-graph-filter="edge.kind"eitherThe same, for links.
data-graph-show="muted", "isolated"a checkbox, a switch, a toggleShows muted records, and records with no visible link.
data-graph-searcha text fieldMarks the records that match what is typed.
data-graph-option="…"a select, radioslabels, edge-labels, colour, depth, and layout, direction, spacing, nodes, curve, arrows, which are on Graph layouts.
data-graph-statsany elementA sentence kept current: {nodes}, {edges}, {matches}, {totalNodes}, {totalEdges}. data-graph-stats-match replaces it while a search marks records.
data-graph-action="…"a buttonselect, focus, unfocus, open, clear, hide, show-all, reset, zoom-in, zoom-out, fit, fullscreen. An action on one record takes the button's value, else the menu's record, else the selection. The rest are on Graph plugins.
data-graph-for="id"a control, or an element around severalNames the graph they drive.

Markup the script fills

AttributeWhereGets
data-graph-field="name"the tooltip, the menu, the panel, a rowThat field of the record, as text. Hidden when the record has none.
data-graph-if="name"any element in the same placesNothing: hidden with the field it names when the record has none, for a label beside a value.
data-graph-iconthe sameThe record's icon class, added to the element's own.
data-graph-tonewritten on the tooltip and the panelThe record's tone, for the app's own styling.
data-graph-tip, data-graph-tip="edge"inside the graphThe tooltip for records, and for links.
data-graph-menua .menu inside the graphOpened on a record, or on the background. data-graph-always on its open item keeps it for a record with no data-href, for an app that opens records itself — Graph in Blazor shows it.
data-graph-detailin the frame, or with data-graph-forShown while a record is selected.
data-graph-neighbours, <template data-graph-neighbour>in the panelOne row per linked record, with data-graph-direction and a [data-graph-link] for the link's label.
data-graph-livea polite live regionThe sentence in data-graph-announce for each record the keyboard reaches, and in data-graph-announce-select for a selection: {label}, {kind}, {meta}, any field, and {links}.

A Blazor app that renders its own panel from @onsedna-graph-select leaves data-graph-detail off; Graph in Blazor shows it.

Keyboard

KeyPointerDoes
← ↑ → ↓hoverThe nearest record that way: lit, named, and said.
Page Down Page Up—The next or previous record linked to this one.
Home—The root, else the most connected record.
SpaceclickSelect, or clear the selection.
Enterdouble clickOpen.
Shift + F10, the menu keyright click, long pressThe menu.
Shift + arrows, + −, 0drag, wheelPan, zoom, fit.
Escclick the backgroundClear the selection, then the ring.

Events

EventDetail
sedna-graph-selectthe record, or { id: null }
sedna-graph-hoverthe recordPointed at, or reached by keyboard.
sedna-graph-openthe recordCancelable: cancelled, data-href is not followed.
sedna-graph-contextthe record, with x and yCancelable: cancelled, the app's menu does not open.
sedna-graph-changethe statsAfter a filter, a search, new data or a fold.

They bubble from the [data-graph] element. A record is { id, label, kind, group, cluster, parent, meta, href, tone, tags, fields, keyboard }.