A measured bar per row against a target tick and shaded qualitative ranges
Tags: chart bullet target kpi progress reporting
The BulletChart compares a measured value with a target on each row: a narrow bar for the actual value, a tick for the target, and shaded bands behind for qualitative ranges such as poor, fair and good. It replaces a wall of gauges with a compact list, which suits goals per builder, community or region.
Svelte only (
@classic-homes/charts-svelte); React support is pending.
Import
<script>
import { BulletChart } from '@classic-homes/charts-svelte';
</script>Basic Usage
<script>
import { BulletChart } from '@classic-homes/charts-svelte';
const data = [
{ name: 'Hilltop Builders', value: 48, target: 55, ranges: [30, 45, 60] },
{ name: 'Riverbend Homes', value: 61, target: 58, ranges: [30, 45, 70] },
{ name: 'Oak & Main', value: 27, target: 40, ranges: [20, 35, 50] },
];
</script>
<BulletChart title="Closings vs Goal, YTD" {data} />Props
Chart-Specific Props
| Prop | Type | Default | Description |
|---|---|---|---|
data |
BulletItem[] |
required | One row per item, first at the top |
rowHeight |
number |
36 |
Height per row for height="auto"; also sizes the target tick |
showLabels |
boolean |
true |
Show value labels at the end of each bar |
labelFormatter |
string | ((params: LabelFormatterParams) => string) |
- | Data label template or function |
valueAxisConfig |
AxisConfig |
- | Value axis customisation (starts at zero) |
onClick |
(params: DataPointEventParams) => void |
- | Callback when a row is clicked; value is the item's value |
Inherited Base Props
| Prop | Type | Default | Description |
|---|---|---|---|
title |
string |
required | Chart title for accessibility (aria-label) |
description |
string |
- | Description for screen readers (generated from the data when omitted) |
height |
number | string |
'auto' |
Chart height in pixels or CSS value |
minHeight |
number |
- | Minimum height in pixels; also bounds an 'auto' height |
maxHeight |
number |
- | Maximum height in pixels; also bounds an 'auto' height |
loading |
boolean |
false |
Show loading skeleton (an overlay when data is already shown) |
error |
string | null |
- | Error message to display |
onRetry |
() => void |
- | Show a Retry button on the error state, calling this |
emptyMessage |
string |
- | Message shown when data is empty |
theme |
'light' | 'dark' | 'auto' |
'auto' |
Theme preference |
animation |
boolean |
true |
Enable/disable animations (respects prefers-reduced-motion) |
showTooltip |
boolean |
true |
Show tooltip on hover |
showGrid |
boolean |
false |
Show grid lines |
valueFormat |
ValueFormat |
- | Preset ('number', 'integer', 'compact', 'currency', 'currencyCents', 'percent', 'percentValue', 'days'), Intl.NumberFormatOptions or a function; drives the value axis, tooltip, labels and data table |
locale |
string |
'en-US' |
Locale for valueFormat |
tooltipFormatter |
string | ((items: TooltipFormatterParams[]) => string) |
- | Custom tooltip template or HTML function |
tooltipValueFormatter |
(value: number) => string |
- | Format values in the default tooltip (overrides valueFormat there) |
option |
EChartsOption |
- | Raw ECharts option deep-merged over the generated one |
onReady |
(chart: EChartsType) => void |
- | Called with the ECharts instance when it is created |
events |
ChartEventHandlers |
- | Raw ECharts event handlers keyed by event name ('datazoom', 'legendselectchanged', ...) |
showDataTable |
boolean | 'sr-only' |
'sr-only' |
Data table for screen readers only, a visible "Show data" toggle (true) or none (false) |
exportable |
boolean | ChartExportConfig |
- | Show PNG and CSV download buttons, or { image, csv, filename } |
patterns |
boolean |
false |
Fill series with decal patterns as well as colour |
class |
string |
- | Custom CSS class |
showLegend |
boolean |
- | Show chart legend |
Data Structure
interface BulletItem {
name: string; // Row label
value: number; // Measured value
target?: number; // Target value, drawn as a tick
ranges?: number[]; // Ascending range boundaries, e.g. [50, 75, 100], shaded behind the bar
color?: string; // Override the bar colour
label?: string; // Label text shown instead of the formatted value
}Examples
Percent of Goal
<BulletChart
title="Pre-Sale Conversion"
data={[
{ name: 'Cedar Ridge', value: 0.42, target: 0.35, ranges: [0.2, 0.3, 0.5] },
{ name: 'Lakeside', value: 0.28, target: 0.35, ranges: [0.2, 0.3, 0.5], color: '#b45309' },
]}
valueFormat="percent"
/>Custom Labels
<BulletChart
title="Homes Under Construction"
data={[
{ name: 'North', value: 84, target: 90, label: '84 of 90' },
{ name: 'South', value: 112, target: 100, label: '112 (over)' },
]}
/>Fixed Height
<BulletChart title="Closings vs Goal" {data} height={240} />Accessibility
- The
titleprop is used asaria-label; a description of how many rows meet their target is generated whendescriptionis omitted - The data is rendered as a screen-reader table with value and target columns (
showDataTable) - Range bands are greyscale shades, so the chart does not rely on hue
- Animations respect
prefers-reduced-motion
Related Charts
- GaugeChart - A single value in a range
- DotPlotChart - Compare values per row
- StatCard - Headline metric with change