# Graph

> Draws any graph of blocks joined parent to child — a node can have several parents as well as several children. The layout runs in layers from top to bottom without measuring anything, so a prerendered page ships the finished picture. Display only: the graph scrolls inside its own viewport, and nothing in it is selectable.

Source: https://ngwr.dev/reference/components/graph  
Kind: Component, Standalone, SSR-safe  
Added in v14.5

## Installation

```angular-ts
import { WrGraph, WrGraphNodeTemplate } from 'ngwr/graph';

// WrGraphNodeTemplate is the <ng-template wrGraphNode> directive in the custom
// node examples below — the selector is wrGraphNode, and imports: [] takes the
// class. A graph that draws its default cards needs WrGraph alone.
@Component({ imports: [WrGraph, WrGraphNodeTemplate] })
export class MyComponent {}
```

```scss
@use 'ngwr/graph';
```

## Nodes and edges

`nodes` are the blocks and `edges` the links, each `{ from, to }` with `from` the parent and `to` the child. Without a template every node is a card showing its `label`. Parents sit above their children, with one exception: no order can put every parent in a cycle above its child, so an edge that closes a cycle climbs back up instead. Every line, that one included, ends in an arrow at the child. The same arrays always give the same picture — on the server, in the browser, run after run — and under `dir="rtl"` the horizontal order mirrors. An edge that names a node which does not exist is skipped, with a warning in dev mode, and neither array is ever written to.

_Also shown on the page: TS._

```angular-html
<wr-graph [nodes]="nodes" [edges]="edges" ariaLabel="Writing workflow" />
```

## Custom nodes

`ng-template wrGraphNode` replaces the default card, and the node arrives as `let-node` with whatever you put in `data`. The box stays the graph's: its size is what the layout reserved — `nodeWidth` / `nodeHeight` for every node, or a node's own `width` / `height`, as the three teams use here — so a template fills that box rather than sizing it. Render `node.label` somewhere in it, because every neighbour's relation text names this node by its label. Below, Platform has two heads and Dev Sandoval reports to both, while Noor Haddad belongs to Design and Research at once. At most widths this chart is wider than the column, and it scrolls inside its own viewport rather than the page.

_Also shown on the page: TS._

```angular-html
<wr-graph [nodes]="nodes" [edges]="edges" [nodeWidth]="176" [nodeHeight]="56" ariaLabel="Team structure">
  <ng-template wrGraphNode let-node>
    @if (node.data.kind === 'unit') {
      <div class="unit">{{ node.label }}</div>
    } @else {
      <div class="person">
        <wr-avatar class="person__avatar" [size]="32" shape="circle">
          <span aria-hidden="true">{{ node.data.initials }}</span>
        </wr-avatar>
        <div class="person__text">
          <span class="person__name">{{ node.label }}</span>
          <span class="person__role">{{ node.data.role }}</span>
        </div>
      </div>
    }
  </ng-template>
</wr-graph>
```

## Shared dependencies

A package graph reads the same way: the parent is the package that depends, the child is its dependency. `@example/signals` is shared by four packages, one of them two layers up, and that longer line passes between the nodes of the layer it crosses rather than through one. The data does not have to be a clean hierarchy either: several edges between one pair of nodes, or an edge that closes a cycle, are laid out without breaking the picture.

_Also shown on the page: TS._

```angular-html
<wr-graph [nodes]="nodes" [edges]="edges" [nodeWidth]="184" [nodeHeight]="52" ariaLabel="Dependencies">
  <ng-template wrGraphNode let-node>
    <div class="package">
      <code class="package__name">{{ node.label }}</code>
      <span class="package__version">{{ node.data.version }}</span>
    </div>
  </ng-template>
</wr-graph>
```

## Accessibility

What the lines show is also written out. Every node is a list item, and after its own content comes a visually hidden sentence naming its parents and then its children, in reading order — so a screen reader hears the structure without the drawing. A node with no edges gets no sentence. The lines and arrowheads are decorative and hidden from assistive technology. The viewport is the one tab stop: a `role="group"` named by `ariaLabel` (falling back to the `graph.label` catalog entry), and focusable so a keyboard can scroll a graph that is larger than its box. Give each graph its own `ariaLabel` when a page holds more than one.

```html
<!-- What the team structure above exposes. The SVG holding the lines is aria-hidden,
     and each <span> is visually hidden. -->
<div role="group" tabindex="0" aria-label="Team structure">
  <div role="list">
    <div role="listitem">Platform <span>Children: Mira Okafor and Tomas Lindqvist.</span></div>
    <!-- … -->
    <div role="listitem">Noor Haddad Product designer <span>Parents: Design and Research.</span></div>
    <!-- … -->
    <div role="listitem">Dev Sandoval Engineer <span>Parents: Mira Okafor and Tomas Lindqvist.</span></div>
  </div>
</div>
```

## What it does not do

The graph is a picture with its relations written out, and nothing more. There is no selection or highlighting, no keyboard movement from one node to the next, and no zoom, fit or panning beyond the viewport's own scrolling. Nothing is editable: nodes cannot be dragged, added, collapsed or grouped. The layout runs top to bottom only, with no left-to-right direction, and lines carry no labels. Node sizes are never measured, so nothing is fitted to its box: the default card cuts off a label that does not fit, and a custom template whose content outgrows its box spills over its neighbours — size the box with `nodeWidth` / `nodeHeight` or a node's own `width` / `height` instead.

## API

| Name | Description | Type | Default |
| --- | --- | --- | --- |
| `nodes` | The nodes, in any order. Input order breaks layout ties, so the same arrays always produce the same picture. Never mutated. | `readonly WrGraphNode<TData>[]` | `[]` |
| `edges` | Parent-child links, `from` the parent and `to` the child. Never mutated. | `readonly WrGraphEdge[]` | `[]` |
| `nodeWidth` | Width in pixels of a node that sets none of its own. | `number` | `160` |
| `nodeHeight` | Height in pixels of a node that sets none of its own. | `number` | `48` |
| `ariaLabel` | Accessible name of the scrolling viewport. Falls back to `graph.label`, then `'Graph'`. | `string \| null` | `null` |

## Types

The data shapes the inputs and the node template take.

| Name | Description | Type | Default |
| --- | --- | --- | --- |
| `WrGraphNode` | One block. `TData` is whatever `data` carries. | `interface` | — |
| `id`required | Unique within the graph. A repeated id is skipped after its first occurrence, with a warning in dev mode. | `string` | — |
| `label`required | The node’s name. The default card shows it, and every neighbour’s relation text reads it out — so a custom template should render it too. | `string` | — |
| `width` | Width in pixels. Falls back to `nodeWidth` when absent, non-finite or not positive. | `number` | — |
| `height` | Height in pixels. Falls back to `nodeHeight` when absent, non-finite or not positive. | `number` | — |
| `data` | Anything the node template needs. Never read by the graph or its layout. | `TData` | — |
| `WrGraphEdge` | A parent-child link, drawn with its arrow at the child. Several edges between one pair, and edges that close a cycle, are both legal. | `interface` | — |
| `from`required | Id of the parent. An edge naming a node that does not exist is skipped, with a warning in dev mode. | `string` | — |
| `to`required | Id of the child. | `string` | — |
| `WrGraphNodeContext` | What an `ng-template wrGraphNode` receives. | `interface` | — |
| `$implicit`required | The node, bound with `let-node`. | `WrGraphNode<TData>` | — |

## CSS variables

Custom properties `ngwr/graph` publishes. Each default below is declared on the component's own selector, so a `:root` override is shadowed by it — set them on that selector, on a wrapper you scope yourself, or inline on the element. Unlike the BEM class names, these are the supported way to restyle the component.

| Variable | Default | Declared on |
| --- | --- | --- |
| `--wr-graph-background` | `var(--wr-color-surface)` | `.wr-graph` |
| `--wr-graph-card-background` | `var(--wr-color-surface)` | `.wr-graph` |
| `--wr-graph-card-border` | `var(--wr-color-outline)` | `.wr-graph` |
| `--wr-graph-card-color` | `var(--wr-color-on-surface)` | `.wr-graph` |
| `--wr-graph-card-font-size` | `var(--wr-text-sm)` | `.wr-graph` |
| `--wr-graph-card-padding` | `0.5rem 0.75rem` | `.wr-graph` |
| `--wr-graph-card-radius` | `var(--wr-border-radius-base)` | `.wr-graph` |
| `--wr-graph-edge-color` | `color-mix(in srgb, var(--wr-color-on-surface-muted) 60%, var(--wr-color-surface))` | `.wr-graph` |
| `--wr-graph-edge-width` | `1px` | `.wr-graph` |
| `--wr-graph-padding` | `1rem` | `.wr-graph` |
