ComponentSSR-safeAdded in v14.5

Graph

Draws blocks joined parent to child, top to bottom, for display only.

Installation

Import the class, and @use the entry point in your global styles: without it the component renders unstyled.

import { Component } from '@angular/core';
import { WrGraph, WrGraphNodeTemplate } from 'ngwr/graph';

@Component({ imports: [WrGraph, WrGraphNodeTemplate] })
export class MyComponent {}

Nodes and edges

Pass nodes and edges; each edge is { from, to }, from parent to child. A node can have several parents.

Idea
Children: Sketch and Outline.
Sketch
Parents: Idea. Children: Draft.
Outline
Parents: Idea. Children: Draft.
Draft
Parents: Sketch and Outline. Children: Publish.
Publish
Parents: Draft.
<wr-graph [nodes]="nodes" [edges]="edges" ariaLabel="Writing workflow" />

Custom nodes

<ng-template wrGraphNode let-node> replaces the card. Render node.label in it: screen readers name the node by it.

Platform
Children: Mira Okafor and Tomas Lindqvist.
Design
Children: Lena Brandt and Noor Haddad.
Research
Children: Noor Haddad and Yara Castell.
Mira OkaforHead of platform
Parents: Platform. Children: Dev Sandoval.
Tomas LindqvistHead of platform
Parents: Platform. Children: Dev Sandoval.
Lena BrandtHead of design
Parents: Design.
Noor HaddadProduct designer
Parents: Design and Research.
Yara CastellHead of research
Parents: Research. Children: Emil Sorensen.
Dev SandovalEngineer
Parents: Mira Okafor and Tomas Lindqvist.
Emil SorensenAnalyst
Parents: Yara Castell.
<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

Cycles and repeated edges between two nodes are allowed.

@example/app1.0.0
Children: @example/router, @example/forms, @example/ui, and @example/signals.
@example/router3.2.1
Parents: @example/app. Children: @example/url-pattern and @example/signals.
@example/forms2.0.4
Parents: @example/app. Children: @example/validators and @example/signals.
@example/ui5.1.0
Parents: @example/app. Children: @example/signals and @example/tokens.
@example/url-pattern0.9.2
Parents: @example/router.
@example/validators2.3.0
Parents: @example/forms.
@example/signals1.8.0
Parents: @example/app, @example/router, @example/forms, and @example/ui.
@example/tokens4.0.0
Parents: @example/ui.
<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

Each node reads out its parents and children. Give every graph on a page its own ariaLabel.

What it does not do

No selection, zoom, editing or edge labels. Content larger than its node box spills over, so size the box.

API

NameDescriptionTypeDefault
nodesThe nodes, in any order. Input order breaks layout ties, so the same arrays always produce the same picture. Never mutated.readonly WrGraphNode<TData>[][]
edgesParent-child links, from the parent and to the child. Never mutated.readonly WrGraphEdge[][]
nodeWidthWidth in pixels of a node that sets none of its own.number160
nodeHeightHeight in pixels of a node that sets none of its own.number48
ariaLabelAccessible name of the scrolling viewport. Falls back to graph.label, then 'Graph'.string | nullnull

Types

NameDescriptionTypeDefault
WrGraphNodeOne block. TData is whatever data carries.interface—
idrequiredUnique within the graph. A repeated id is skipped after its first occurrence, with a warning in dev mode.string—
labelrequiredThe node’s name, read by screen readers; render it in a custom template.string—
widthWidth in pixels. Falls back to nodeWidth when absent, non-finite or not positive.number—
heightHeight in pixels. Falls back to nodeHeight when absent, non-finite or not positive.number—
dataAnything the node template needs. Never read by the graph or its layout.TData—
WrGraphEdgeA parent-child link; repeated edges and cycles are allowed.interface—
fromrequiredId of the parent. An edge naming a node that does not exist is skipped, with a warning in dev mode.string—
torequiredId of the child.string—
WrGraphNodeContextWhat an ng-template wrGraphNode receives.interface—
$implicitrequiredThe node, bound with let-node.WrGraphNode<TData>—

CSS variables

Set these on the component's selector or a wrapper, not on :root: the defaults are declared on ngwr/graph's own element.

VariableDefaultDeclared on
--wr-graph-backgroundvar(--wr-color-surface).wr-graph
--wr-graph-card-backgroundvar(--wr-color-surface).wr-graph
--wr-graph-card-bordervar(--wr-border-base).wr-graph
--wr-graph-card-colorvar(--wr-color-on-surface).wr-graph
--wr-graph-card-font-sizevar(--wr-text-sm).wr-graph
--wr-graph-card-line-heightvar(--wr-leading-tight).wr-graph
--wr-graph-card-paddingvar(--wr-space-sm) var(--wr-space-md).wr-graph
--wr-graph-card-radiusvar(--wr-border-radius-base).wr-graph
--wr-graph-edge-colorcolor-mix(in srgb, var(--wr-color-on-surface-muted) 60%, var(--wr-color-surface)).wr-graph
--wr-graph-edge-width1px.wr-graph
--wr-graph-font-familyvar(--wr-font-family-base).wr-graph
--wr-graph-paddingvar(--wr-space-lg).wr-graph
--wr-graph-viewport-radiusvar(--wr-border-radius-base).wr-graph

See also