import { Component } from '@angular/core';
import { WrBulletChart } from 'ngwr/charts/bullet-chart';
@Component({ imports: [WrBulletChart] })
export class MyComponent {}Bullet Chart
One measure against its target, over qualitative bands: Stephen Few's bullet graph, a compact replacement for a gauge.
Installation
Import the class, and @use the entry point in your global styles: without it the component renders unstyled.
Basic
The value is the bar, the target the marker across it. ranges are the upper limits of the bands, drawn as greys that fade from zero outwards.
<wr-bullet-chart
label="Revenue"
subLabel="USD, thousands"
[value]="270"
[target]="250"
[ranges]="[150, 225, 300]"
/>A column of measures
Give the label column one width with --wr-bullet-chart-label-width, and stacked bullets line up.
@for (kpi of kpis; track kpi.label) {
<wr-bullet-chart
style="--wr-bullet-chart-label-width: 7rem"
[label]="kpi.label"
[subLabel]="kpi.subLabel"
[value]="kpi.value"
[target]="kpi.target"
[ranges]="kpi.ranges"
/>
}Scale
With no max the scale ends at the largest of the value, the target and the ranges. A fixed max draws anything past it to the end. A negative value starts the scale below zero, and a narrow chart prints fewer ticks.
<!-- Ends at 300, the largest of the three -->
<wr-bullet-chart label="Default" [value]="270" [target]="250" [ranges]="[150, 225, 300]" />
<!-- A fixed end: 270 is drawn to it -->
<wr-bullet-chart label="max 250" [value]="270" [target]="200" [ranges]="[150, 225, 300]" [max]="250" />
<!-- Below zero: the bar runs from -20 up to zero -->
<wr-bullet-chart label="Margin" subLabel="%" [value]="-20" [target]="10" [ranges]="[0, 20, 40]" />Number format
Numbers follow LOCALE_ID. valueFormat replaces it for the ticks, the tooltip and the accessible name.
<wr-bullet-chart label="Revenue" [value]="270" [target]="250" [ranges]="[150, 225, 300]" [valueFormat]="format" />const usd = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', notation: 'compact' });
// The figures are thousands: 270 reads "$270K".
format = (value: number): string => usd.format(value * 1000);Missing values
A value that is not a number draws no bar, and the accessible name says there is none rather than reading zero. Without a target there is no marker.
<!-- No value yet: no bar, and the name says so -->
<wr-bullet-chart label="Revenue" [value]="null" [target]="250" [ranges]="[150, 225, 300]" />
<!-- No target: no marker -->
<wr-bullet-chart label="Revenue" [value]="270" [ranges]="[150, 225, 300]" />Accessibility
| Part | Behaviour |
|---|---|
| Drawing | One role="img", named by a sentence: "Revenue: 270 of target 250". ariaLabel replaces it. |
| Labels | Hidden from screen readers, since the name already carries the label. The sub-label is the drawing's description. |
| Bands | Not announced: they have no names. Say what they mean in ariaLabel where it matters. |
| Colour | The bands differ in lightness, and the bar and the marker in shape, so none of it relies on hue. |
| Keyboard | No tab stop. The tooltip is pointer only and says what the name says. |
API
| Name | Description | Type | Default |
|---|---|---|---|
valuerequired | The measure. A value that is not a finite number — null while it loads — draws no bar, and the chart is announced as having no value rather than as zero. | number | null | — |
target | The comparative measure, drawn as a marker across the line. null, or anything that is not a finite number, draws none and leaves it out of the accessible name. | number | null | null |
ranges | The upper limit of each qualitative band, in any order: [150, 225, 300] draws a band from the scale's start to 150, one from 150 to 225 and one from 225 to 300. They are sorted, a repeated limit counts once, and one that is not a finite number is dropped. The band nearest zero is the strongest grey and each one after it fainter, between --wr-bullet-chart-range-start and --wr-bullet-chart-range-end. Few recommends no more than five. The first band is everything below the first limit, so it has room only where the scale reaches below that limit. A negative first limit lower than both the value and the target is where the scale starts, which leaves that band no room: it is not drawn, and the drawing opens with the second band and its paler grey. Where the scale runs on past the last limit, a rule marks where the last band ends. | readonly number[] | [] |
max | The end of the scale. 0 takes the largest of value, target and ranges, so the last band reaches the end; a value above 0 fixes it, and anything past it is drawn to the end — the accessible name and the tooltip still give its own figure. | number | 0 |
label | What is measured, shown at inline start beside the drawing, ellipsised past --wr-bullet-chart-label-max-width. It heads the tooltip and opens the accessible name; without it the name opens with bulletChart.label ("Bullet chart"). | string | null | null |
subLabel | A line under the label — the unit, the period. The drawing is described by it (aria-describedby), so a screen reader hears it after the name. | string | null | null |
ariaLabel | Accessible name of the drawing, in place of the sentence the chart writes itself — "Revenue: 270 of target 250". Give one where the bands mean something a reader needs, since they have no names of their own. | string | null | null |
valueFormat | Writes every number the chart shows: the scale's ticks, the tooltip and the accessible name. null uses Intl.NumberFormat in LOCALE_ID, with compact ticks (1.2K) unless the full numbers are shorter. | ((value: number) => string) | null | null |
showScale | Show the scale's ticks under the drawing. | boolean | true |
tooltip | Show a tooltip with the label, the value and the target while the pointer is over the drawing. Pointer only: the accessible name says the same. | boolean | true |
CSS variables
Set these on the component's selector or a wrapper, not on :root: the defaults are declared on ngwr/charts/bullet-chart's own element.
| Variable | Default | Declared on |
|---|---|---|
--wr-bullet-chart-bar-thickness | calc(100% / 3) | .wr-bullet-chart |
--wr-bullet-chart-color | var(--wr-color-on-surface) | .wr-bullet-chart |
--wr-bullet-chart-font-family | var(--wr-font-family-base) | .wr-bullet-chart |
--wr-bullet-chart-label-color | var(--wr-color-on-surface) | .wr-bullet-chart |
--wr-bullet-chart-label-font-size | var(--wr-text-sm) | .wr-bullet-chart |
--wr-bullet-chart-label-font-weight | var(--wr-font-weight-semibold) | .wr-bullet-chart |
--wr-bullet-chart-label-gap | var(--wr-space-md) | .wr-bullet-chart |
--wr-bullet-chart-label-max-width | 12rem | .wr-bullet-chart |
--wr-bullet-chart-label-width | fit-content(var(--wr-bullet-chart-label-max-width)) | .wr-bullet-chart |
--wr-bullet-chart-range-edge-color | var(--wr-color-on-surface-muted) | .wr-bullet-chart +1 variant override |
--wr-bullet-chart-range-end | var(--wr-color-fill-strong) | .wr-bullet-chart |
--wr-bullet-chart-range-start | color-mix(in srgb, var(--wr-color-on-surface-muted) 50%, var(--wr-color-fill-strong)) | .wr-bullet-chart |
--wr-bullet-chart-scale-gap | var(--wr-space-xs) | .wr-bullet-chart |
--wr-bullet-chart-sub-label-color | var(--wr-color-on-surface-muted) | .wr-bullet-chart |
--wr-bullet-chart-sub-label-font-size | var(--wr-text-xs) | .wr-bullet-chart |
--wr-bullet-chart-sub-label-line-height | var(--wr-text-xs-leading) | .wr-bullet-chart |
--wr-bullet-chart-target-color | var(--wr-color-on-surface) | .wr-bullet-chart +1 variant override |
--wr-bullet-chart-target-length | 75% | .wr-bullet-chart |
--wr-bullet-chart-target-ring | var(--wr-color-surface) | .wr-bullet-chart +1 variant override |
--wr-bullet-chart-target-ring-width | 1px | .wr-bullet-chart |
--wr-bullet-chart-target-thickness | 3px | .wr-bullet-chart |
--wr-bullet-chart-tick-color | var(--wr-color-on-surface-muted) | .wr-bullet-chart |
--wr-bullet-chart-tick-font-size | var(--wr-text-xs) | .wr-bullet-chart |
--wr-bullet-chart-tick-length | var(--wr-space-xs) | .wr-bullet-chart |
--wr-bullet-chart-tick-line-height | var(--wr-text-xs-leading) | .wr-bullet-chart |
--wr-bullet-chart-track-color | var(--wr-color-fill) | .wr-bullet-chart +1 variant override |
--wr-bullet-chart-track-height | 1.5rem | .wr-bullet-chart |
--wr-bullet-chart-track-min-width | 6rem | .wr-bullet-chart |
--wr-bullet-chart-track-radius | var(--wr-border-radius-sm) | .wr-bullet-chart |
--wr-bullet-chart-value-color | var(--wr-color-on-surface) | .wr-bullet-chart +1 variant override |
Dependencies
| Built on | Popover |
|---|
See also
Component GaugeA read-only semicircle dial for a single value.Component Meter GroupOne bar split into labelled parts, with a legend.Component Bar ChartBars for one or several series, grouped or stacked, standing up or lying along a row, with lines over them.Component ProgressA bar that shows how far a task has got.Component PopoverShow a template, or a text tooltip, next to its trigger.