Orbex Sonar-DB Design system , home

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.

Orders of a month, and an overdue share
May orders
Overdue share of the balance
Code
tsx
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.

Two and three parts
Inventory
May orders
2,600plt
Code
tsx
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.

Daily shipments in April

Code
tsx
/* 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

DoColor by role: the first part in accent, then strong, the rest in the rest tone.
Don’tWarning and danger for parts that are not alerts: the chart cries wolf.
DoLabels beside the chart: the card states the key figures in text.
Don’tA chart with no figure anywhere: its shape alone has to be decoded.

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

The tones, as a tick bar and in the legend
Chart roles
Token Preview LightDark
--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.