# Bullet Chart

> One measure against its target, over qualitative bands: Stephen Few's bullet graph, a compact replacement for a gauge.

Source: https://ngwr.dev/charts/bullet-chart  
Added in v15.1

## Installation

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

```typescript
import { Component } from '@angular/core';
import { WrBulletChart } from 'ngwr/charts/bullet-chart';

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

```scss
@use 'ngwr/charts/bullet-chart';
```

## 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.

```angular-html
<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.

```angular-html
@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.

```angular-html
<!-- 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.

```angular-html
<wr-bullet-chart label="Revenue" [value]="270" [target]="250" [ranges]="[150, 225, 300]" [valueFormat]="format" />
```

```angular-ts
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.

```angular-html
<!-- 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 |
| --- | --- | --- | --- |
| `value`required | 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](https://ngwr.dev/reference/components/popover) |
| --- | --- |

## See also

- [Gauge](https://ngwr.dev/charts/gauge) — A read-only semicircle dial for a single value.
- [Meter Group](https://ngwr.dev/charts/meter-group) — One bar split into labelled parts, with a legend.
- [Bar Chart](https://ngwr.dev/charts/bar-chart) — Bars for one or several series, grouped or stacked, standing up or lying along a row, with lines over them.
- [Progress](https://ngwr.dev/reference/components/progress) — A bar that shows how far a task has got.
- [Popover](https://ngwr.dev/reference/components/popover) — Show a template, or a text tooltip, next to its trigger.
