Charts
Three shapes cover the overview: a tick bar for a share, a ring for the parts of a whole, a line for a month day by day, and one legend for all of them. They are drawn in SVG and CSS with the chart roles of the tokens; no charting library.
Tick bar
One row of fine ticks, each part in its share of the width and in its chart role. The parts meet without a gap, and a legend under the bar names each color.
Code
import { ChartLegend } from '@orbex/sonar-components/chart-legend'
import { TickBar } from '@orbex/sonar-components/tick-bar'
const orders = [
{ value: 1420, tone: 'accent', label: 'Shipped' },
{ value: 80, tone: 'strong', label: 'On hold' },
{ value: 1100, tone: 'muted', label: 'Pending' },
] as const
const balance = [
{ value: 2_230_800, tone: 'danger', label: 'Overdue' },
{ value: 1_669_200, tone: 'muted', label: 'Not due yet' },
] as const
const plt = (value: number) => `${value.toLocaleString('en-US')} plt`
const usd = (value: number) => `${value.toLocaleString('en-US')} USD`
export default function TickBars() {
return (
<div className="chart-demo" data-shape="bars">
<figure>
<figcaption>May orders</figcaption>
<TickBar label="May orders" format={plt} segments={[...orders]} />
<ChartLegend aria-hidden="true" items={orders.map(({ label, tone }) => ({ label, tone }))} />
</figure>
<figure>
<figcaption>Overdue share of the balance</figcaption>
<TickBar label="Overdue share of the balance" format={usd} segments={[...balance]} />
<ChartLegend aria-hidden="true" items={balance.map(({ label, tone }) => ({ label, tone }))} />
</figure>
</div>
)
}
Ring chart
Parts of a whole as a ring of 96 ticks, the Inventory donut. The hole can hold the total; the legend names each color.
Code
import { ChartLegend } from '@orbex/sonar-components/chart-legend'
import { RingChart } from '@orbex/sonar-components/ring-chart'
const inventory = [
{ value: 840, tone: 'danger', label: 'On hold' },
{ value: 1960, tone: 'muted', label: 'Available' },
] as const
const orders = [
{ value: 1420, tone: 'accent', label: 'Shipped' },
{ value: 80, tone: 'strong', label: 'On hold' },
{ value: 1100, tone: 'muted', label: 'Pending' },
] as const
const plt = (value: number) => `${value.toLocaleString('en-US')} plt`
export default function Rings() {
return (
<div className="chart-demo" data-shape="rings">
<figure>
<figcaption>Inventory</figcaption>
<RingChart label="Inventory" format={plt} style={{ width: 128 }} segments={[...inventory]} />
<ChartLegend aria-hidden="true" items={inventory.map(({ label, tone }) => ({ label, tone }))} />
</figure>
<figure>
<figcaption>May orders</figcaption>
<RingChart label="May orders" format={plt} style={{ width: 160 }} segments={[...orders]}>
<span className="ring-total">
<strong>2,600</strong>
<span>plt</span>
</span>
</RingChart>
<ChartLegend aria-hidden="true" items={orders.map(({ label, tone }) => ({ label, tone }))} />
</figure>
</div>
)
}
Trend chart
A month day by day as a line, with its running total dotted against a second axis. The legend turns each series off and on; hovering, touching or the arrow keys read a day.
Code
/* A month day by day, with its running total: daily shipments in April */
import { TrendChart } from '@orbex/sonar-components/trend-chart'
import '@orbex/sonar-components/trend-chart.css'
const shipped = [
355, 375, 310, 510, 405, 430, 325, 305, 300, 285, 405, 455, 370, 295, 350, 445, 450, 395, 365, 335, 310, 360, 345, 285,
485, 360, 365, 340, 330, 375,
]
const days = shipped.map((_, index) => `Apr ${index + 1}`)
const total = shipped.reduce((sum, value) => sum + value, 0)
export default function Trend() {
return (
<div style={{ width: 'min(100%, 560px)' }}>
<TrendChart
label={`Daily shipments in April, ${total.toLocaleString('en-US')} pallets in total`}
categories={days}
series={{ name: 'Shipments (plt)', values: shipped }}
cumulative={{ name: 'Cumulative (plt)' }}
unit="plt"
axisLabels={[0, 14, days.length - 1]}
/>
</div>
)
}
Rules
- Color by role. The first series in accent, the second in the strong neutral, the rest of a whole in the rest tone, alerts in danger. Never a hue that means nothing.
- A series that is off keeps its name readable. It drops to the tertiary level at full opacity and its marker turns hollow; it is never faded.
- Figures in the data role, the mono face at 10 px, with tabular numerals; gridlines dotted and quiet.
- Labels beside the chart, not inside it. The card states the key figures in text; the chart shows their shape.
- No animation on load. A chart draws at once; values move only when the unit or the period changes.
Do and don’t
Accessibility
Keyboard
| Key | What it does |
|---|---|
| Tab | Reaches the trend chart and its legend buttons. Tick bars and rings take no focus. |
| Left and Right arrows | In the trend chart: read the previous or next day. |
| Home and End | Go to the first day or the last day with data. |
| Escape | Clears the day being read. |
| Enter or Space | On a legend button: shows or hides that series. |
Roles and attributes
| Element or attribute | What it gives |
|---|---|
role="img" with aria-label |
Tick bars and rings: one name lists every part with its value. |
role="group" |
The trend chart, named by its label with a hint to use the arrow keys. |
aria-live="polite" |
The trend chart reads the chosen day: “May 12: 350 plt, 4,485 plt so far”. |
aria-pressed |
Legend buttons say whether their series is shown. |
| Chart legend | aria-hidden when the chart’s own name already lists the parts, as in every demo here. |
Notes
- A chart is never the only way to a figure: the card states its key figures in text beside it.
- Color is never the only cue: a legend names every color, and a series that is off keeps its name readable.
- The parts of one chart differ in lightness as well as hue (accent, strong and rest; or danger and rest), so they stay distinct without color vision.
Chart roles
| Token | Preview | Light | Dark |
|---|---|---|---|
--sonar-color-chart-primary
First series. |
accent
#1A8838
|
accent
#76B27C
|
|
--sonar-color-chart-secondary
Second series (cumulative). |
palette.black
#000000
|
palette.neutral-dark.90
#DCE5D9
|
|
--sonar-color-chart-alert
Values that need attention, such as stock on hold. |
danger
#DD3535
|
danger
#F04B4B
|
|
--sonar-color-chart-rest
Remainder of a whole: the rest of a donut or bar. |
palette.neutral.76
#B5BEB2
|
palette.neutral-tinted.36
#4C584A
|
|
--sonar-color-chart-grid
Grid lines. |
palette.neutral.90
#DDE5D9
|
palette.neutral.21
#2D352C
|
|
--sonar-color-chart-axis
Axis figures. |
text.default
#2D352C
|
text.default
#DDE5D9
|
|
--sonar-color-chart-tip-shadow
The chart bubble’s drop shadow; none in dark, where the bubble is light. |
palette.black
#0000001F
|
palette.black
#00000000
|
Built with
Each chart is its own component, with its API: Tick bar, Ring chart, Trend chart and Chart legend. The trend chart has a budget of 4 KB instead of 3: it draws axes, two series, a legend and reading by pointer and keys.