ComponentAdded in v15.1

Funnel Chart

How many reached each stage of a sequence, and how many of them went on to the next.

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 { WrFunnelChart } from 'ngwr/charts/funnel-chart';

@Component({ imports: [WrFunnelChart] })
export class MyComponent {}

Basic

One centred bar per stage, as wide as its share of the largest. Between two bars, the share of the stage above; under each value, the share of the first.

<wr-funnel-chart
  ariaLabel="Sign-up funnel"
  [stages]="[
    { label: 'Visited', value: 12000 },
    { label: 'Signed up', value: 4800 },
    { label: 'Activated', value: 2880 },
    { label: 'Paid', value: 720 }
  ]"
/>

Conversions

conversion picks what the drawing prints. The tooltip, each stage's accessible name and the data table keep both.

<wr-funnel-chart ariaLabel="Sign-up funnel" [stages]="signups" [conversion]="conversion()" />
conversion = signal<WrFunnelConversion>('both'); // 'previous' | 'first' | 'none'

Growth, gaps and zeros

Nothing is sorted or clamped. A stage larger than the one before is drawn wider, above 100%. A missing value draws no bar and no band. Zero is a hairline.

<wr-funnel-chart ariaLabel="Email campaign" [stages]="campaign" />
campaign: WrFunnelStage[] = [
  { label: 'Emailed', value: 5000 },
  { label: 'Opened', value: 2100 },
  // More than the stage before: drawn wider, 123.8% of previous.
  { label: 'Shared', value: 2600 },
  // Not measured: no bar, no band, no conversion to or from it.
  { label: 'Clicked', value: NaN },
  // A real zero: a hairline.
  { label: 'Converted', value: 0 },
];

Shared scale

With no max the largest stage is the full width. Give two funnels the same max to compare them.

<!-- One full width for both: the larger funnel's top stage -->
<wr-funnel-chart ariaLabel="Web" [stages]="web" [max]="9000" />
<wr-funnel-chart ariaLabel="Mobile" [stages]="mobile" [max]="9000" />

Number format

Values and conversions follow LOCALE_ID. valueFormat replaces the values; percentFormat the conversions, and it takes a ratio, so 0.25 is a quarter.

<wr-funnel-chart ariaLabel="Orders by value" [stages]="orders" [valueFormat]="money" [percentFormat]="percent" />
usd = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', notation: 'compact' });
money = (value: number) => this.usd.format(value);

// A ratio in, as Intl's percent style takes it: 0.25 is a quarter.
percents = new Intl.NumberFormat('en-US', { style: 'percent', minimumFractionDigits: 2 });
percent = (ratio: number) => this.percents.format(ratio);

Data table

dataTable adds a table only screen readers reach: a row per stage, with its value and both conversions. Nothing changes on screen.

<wr-funnel-chart [stages]="signups" ariaLabel="Sign-up funnel" dataTable />
Sign-up funnel
StageValueFrom previousFrom first
Visited12,000
Signed up4,80040%40%
Activated2,88060%24%
Paid72025%6%

Accessibility

PartBehaviour
ChartA role="group", named by ariaLabel.
StageA role="img" named with its label, value and both conversions.
BandsHidden. The share between two bars is already in the lower stage's name.
ColourCarries nothing. Every stage is named in text, so one colour is the default.
KeyboardNo tab stop. The tooltip is pointer only and repeats the names.

API

NameDescriptionTypeDefault
stagesThe stages, from the top of the funnel down. Nothing is sorted: a stage is drawn where it is given, so a stage larger than the one before it shows as one.readonly WrFunnelStage[][]
ariaLabelAccessible name of the chart, and the data table's caption. Each stage names itself, so this says what the funnel is about. Falls back to funnelChart.label, then 'Funnel chart'.string | nullnull
colorThe colour of a stage with no color of its own. One colour by default, because the stages are one quantity measured along a sequence, not categories: every stage is named in text beside its bar, so colour tells nothing apart here.string'var(--wr-color-primary)'
maxThe value of a full-width bar. 0 makes it the largest stage shown; a value above 0 fixes it, so two funnels side by side share one scale. A stage above a fixed max is drawn full width, and its figure, the tooltip and the data table still give its own value.number0
conversionWhich conversions are printed on the drawing: each stage's share of the stage before it, between the two bars (previous), its share of the first stage, under its value (first), both or none. The tooltip, every stage's accessible name and the data table carry both whatever this says.WrFunnelConversion'both'
showValuesPrint each stage's value beside its bar.booleantrue
valueLabelWhat the values count — visitors, orders — as the heading of the data table's value column. Falls back to funnelChart.value ("Value").string | nullnull
tooltipShow a tooltip with the stage's label, value and both conversions on hover. Pointer only — each stage's accessible name already carries the same figures.booleantrue
valueFormatWrites every value the chart shows: beside the bars, in the tooltip, in the accessible names and in the data table. null uses Intl.NumberFormat in LOCALE_ID.((value: number) => string) | nullnull
percentFormatWrites every conversion the chart shows, given as a RATIO — 0.25 for a quarter, 1.2 for a stage that grew by a fifth — the number Intl.NumberFormat's percent style takes, so one of those can be passed as it is. null uses that style in LOCALE_ID, with up to one fraction digit, and two significant digits below 1% so a small real conversion is not printed as 0%.((ratio: number) => string) | nullnull
dataTableRender the chart's figures as a table that only assistive technology reaches: a row per stage, headed by its label, with its value and both conversions, each written as the tooltip writes it. A figure the stage does not have is an empty cell. Each stage's accessible name already carries its figures; the table is for a reader who wants to move through them cell by cell, with the column headings read out. It is rendered beside the stages, clipped to nothing: it takes no room, draws nothing and holds no tab stop.booleanfalse

Types

interface WrFunnelStage {
  label: string;
  value: number;
  color?: string;
}

type WrFunnelConversion = 'both' | 'previous' | 'first' | 'none';
NameDescriptionTypeDefault
WrFunnelStageOne stage.interface—
labelrequiredBeside the bar, in the tooltip and in the table.string—
valuerequiredHow many reached it. A non-finite value is missing.number—
colorCSS colour for the bar and its bands.stringthe chart's `color`
WrFunnelConversionWhich conversions the drawing prints.'both' | 'previous' | 'first' | 'none'—

CSS variables

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

VariableDefaultDeclared on
--wr-funnel-chart-active-bgvar(--wr-color-hover).wr-funnel-chart
--wr-funnel-chart-band-opacity0.2.wr-funnel-chart
--wr-funnel-chart-bar-heightcalc(var(--wr-funnel-chart-value-line-height) + var(--wr-funnel-chart-conversion-line-height)).wr-funnel-chart
--wr-funnel-chart-bar-radiusvar(--wr-border-radius-sm).wr-funnel-chart
--wr-funnel-chart-colorvar(--wr-color-on-surface).wr-funnel-chart
--wr-funnel-chart-column-gapvar(--wr-space-md).wr-funnel-chart
--wr-funnel-chart-conversion-colorvar(--wr-color-on-surface-muted).wr-funnel-chart
--wr-funnel-chart-conversion-font-sizevar(--wr-text-xs).wr-funnel-chart
--wr-funnel-chart-conversion-line-heightvar(--wr-text-xs-leading).wr-funnel-chart
--wr-funnel-chart-font-familyvar(--wr-font-family-base).wr-funnel-chart
--wr-funnel-chart-gap-height1.5rem.wr-funnel-chart
--wr-funnel-chart-gap-label-bgcolor-mix(in srgb, var(--wr-color-surface) 85%, transparent).wr-funnel-chart
--wr-funnel-chart-gap-label-padding-xvar(--wr-space-xs).wr-funnel-chart
--wr-funnel-chart-gap-label-radiusvar(--wr-border-radius-sm).wr-funnel-chart
--wr-funnel-chart-label-colorvar(--wr-color-on-surface).wr-funnel-chart
--wr-funnel-chart-label-font-sizevar(--wr-text-sm).wr-funnel-chart
--wr-funnel-chart-label-line-heightvar(--wr-text-sm-leading).wr-funnel-chart
--wr-funnel-chart-label-max-width12rem.wr-funnel-chart
--wr-funnel-chart-stage-padding-xvar(--wr-space-xs).wr-funnel-chart
--wr-funnel-chart-stage-radiusvar(--wr-border-radius-sm).wr-funnel-chart
--wr-funnel-chart-track-min-width40%.wr-funnel-chart
--wr-funnel-chart-value-colorvar(--wr-color-on-surface).wr-funnel-chart
--wr-funnel-chart-value-font-sizevar(--wr-text-sm).wr-funnel-chart
--wr-funnel-chart-value-font-weightvar(--wr-font-weight-medium).wr-funnel-chart
--wr-funnel-chart-value-line-heightvar(--wr-text-sm-leading).wr-funnel-chart

Dependencies

Built onPopover

See also