Radial column charts
radialColumn: layers, links, keys, markers, encodings, goals, gauge, chord, readable labels, API.
A radial column chart arranges one slim bar per category around a circle or an arc. Inside the ring you can stack any number of layers (pies, donuts, sunbursts, goal rings, a live gauge, a chord diagram and more), and you can link what the layers mean to the bars outside. One chart then answers "how much, when, of what kind, and against which goal" at once. A typical use is an operations room showing 24 hours of requests around the rim, the services inside, and error budgets as rings.
radialColumn is part of west-coast-charts/premium (see Premium and licensing). Every
option below is typed in west-coast-charts/premium and listed in the
premium options reference.
- A first ring
- Shape: angles, radii, bars
- Layers
- Linking layers to the bars
- Semantic keys, ring labels and rim markers
- Encoding extra metrics (colour, delta)
- Goal rings and the live gauge
- Chord layer
- Readable labels
- Runtime API and events
- Accessibility
- Performance tips
All examples assume this setup:
import WestCoastCharts from 'west-coast-charts'
import premium from 'west-coast-charts/premium'
WestCoastCharts.use(premium)A first ring
The column ring takes the same data shape as a bar chart: series plus xaxis.categories, or
{ x, y } points.
import WestCoastCharts from 'west-coast-charts'
import premium from 'west-coast-charts/premium'
WestCoastCharts.use(premium)
const hours = Array.from({ length: 24 }, (_, h) => `${String(h).padStart(2, '0')}:00`)
const requests = hours.map((_, h) => Math.round(300 + 250 * Math.sin(((h - 6) / 24) * 2 * Math.PI) + (h % 3) * 20))
const chart = new WestCoastCharts(document.querySelector('#chart'), {
chart: { type: 'radialColumn', height: 420 },
series: [{ name: 'Requests / h', data: requests }],
xaxis: { categories: hours },
plotOptions: {
radialColumn: {
valueAxis: { tickAmount: 4 },
center: { label: 'Requests today' },
},
},
})
await chart.render()What you get without further options: a spectrum fill by angle for a single series, a value scale with grid rings, category labels that thin out to a readable step, the total in the centre (it shows the hovered slot while you hover), a tooltip, keyboard navigation and a screen-reader summary.
Readable density on small screens
Start with the outer ring and at most two inner layers. A 144-slot heat ring with several nested layers can be useful in a large operational view and unreadable on a phone. Keep a phone version explicit: for example show 24 hourly slots and one service donut; retain the complete data in a separate table or detailed view. Reducing a view must never silently drop source measurements or change their totals.
The renderer emits one WCC_RADIAL_READABILITY warning per chart when a layer ring
is thinner than 8 px or heat cells have less than 2 px of arc per slot. The warning
names the affected layers. It leaves every layer and value intact.
plotOptions.radialColumn.readabilityWarnings: false disables this developer
diagnostic when the density is a deliberate choice; it does not improve legibility.
Before: 144 time slots plus five inner layers at 390 px. Recommended compact view: 24 explicitly aggregated hourly slots plus one inner layer, with the aggregation named in its label and the unaggregated data available separately.
Compact layouts in the operational gallery
The dense gallery examples use the existing top-level responsive option at
600 px. These are explicit layout alternatives, not a switch that disables the
readability diagnostic. Their default compact configurations retain their source
time slots, rows and values:
quint-radialandorient-quintshare space by actual ring count instead of assigning the six budget rings a small fixed percentage. On a phone the centre and gaps shrink and the full legend moves below the dial. All 144 ten-minute slots, six budget values and both named three-row heatmap selections remain. Those three-row story selections were already explicit in the desktop example; they are not a phone aggregate of all six service/cluster rows. The optional All 6 rows (wide view) control requests every row and a taller wide view; it does not silently substitute the three-row selection on narrow screens.matrix-meshretains all 48 service/window latency observations and the full eight-service calls matrix. In the compact layout the eight current budget percentages become one service-aligned heat band. Each current percentage is repeated unchanged over that service's six windows; this is a current snapshot, not six historical budget measurements. Percentages are never added together. Short outer service labels use the same identities as the full matrix headers and tooltip names.goals-teamandorient-goalsretain all seven teams and thirty days;heat-weekretains all seven days × 24 hours and all five channels;saas-ops-quadretains all request slots, six budgets, six cluster costs and both traffic hierarchy levels.chord-meshretains every service, call and ownership node, with a deliberate readable chord-arc width.
The compact layouts do not aggregate or discard source measurements. If an application instead chooses hourly aggregation, it must define the statistic for each metric (sum for counts, a deliberate maximum or weighted measure for rates), rebuild links/markers for the new slots and label that choice. A label saying "hourly" cannot make a subsampled ten-minute series an hourly total.
Shape: angles, radii, bars
Option (plotOptions.radialColumn.*) |
Default | What it does |
|---|---|---|
startAngle, endAngle |
0, 360 |
Degrees, 0 = 12 o'clock, clockwise. A partial range such as -120 … 120 makes a gauge-like arc; the chart is fitted to the arc. |
innerRadius |
'auto' |
Where the bars start: px, '40%' or a 0–1 fraction of the outer radius. |
barPadding |
'auto' |
Gap between slots (fraction of a slot). |
groupPadding |
0.14 |
Gap between bars of one slot when several series are grouped. |
stacked |
chart.stacked |
Stack series outward (negative values grow inward). |
rounded |
true |
Rounded bar ends. |
fill |
{ type: 'auto' } |
'spectrum' (by angle), 'series', 'value' (by magnitude) with your stops; fade darkens bars towards their base. |
track |
{ show: false } |
A faint full-height slot behind each bar. |
valueAxis |
min, max, tickAmount, nice, rings, labels (see Readable labels). |
|
categoryLabels |
orientation ('auto', 'horizontal', 'radial'), step, maxCount, formatter, maxWidth, offset. A formatter that returns '' hides that label and frees its space. |
|
center |
show ('auto' hides it in small holes), label, value, formatter, hover. |
|
legend |
show: 'auto', position: 'auto' | 'right' | 'bottom'. |
A half-ring with two stacked series and a value-coloured fill:
import WestCoastCharts from 'west-coast-charts'
import premium from 'west-coast-charts/premium'
WestCoastCharts.use(premium)
const days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
await new WestCoastCharts(document.querySelector('#chart'), {
chart: { type: 'radialColumn', height: 320, stacked: true },
series: [
{ name: 'Successful builds', data: [42, 51, 47, 55, 60, 12, 9] },
{ name: 'Failed builds', data: [3, 6, 2, 4, 7, 1, 0] },
],
xaxis: { categories: days },
plotOptions: {
radialColumn: {
startAngle: -90,
endAngle: 90,
innerRadius: 0.45,
categoryLabels: { orientation: 'horizontal' },
center: { label: 'Builds this week' },
},
},
}).render()Layers
plotOptions.radialColumn.layers lists the inner rings, innermost first. Every layer can carry its
own data or borrow a series by name (series: 'Mix'); a borrowed series leaves the column ring.
type |
Data | Notes |
|---|---|---|
pie |
numbers, [name, value] or { x, y, color } |
Fills to the centre when it is the first layer. |
donut |
as pie | One ring. |
nestedDonut |
[{ name, data }] or number[][] |
One ring per entry; a category keeps its colour on every ring. |
sunburst |
{ x, y, children } nodes |
One ring per depth. Children take tones of their parent. |
arc, radialBar |
{ x, y, max } |
One progress ring per value over a track (min/max per layer, default 0–100). |
polarArea |
as pie | Equal angles, radius by value. |
heatmap |
[{ name, data }] or number[][], null = no value |
Rings of cells aligned with the bars; a colour ramp per layer (scale: 'log' spaces it by ratio). |
goals |
{ x, y, target, color, label } |
Activity-style rings that lap past 100 %. See Goal rings. |
gauge |
value, data: [n] or follow: '<series>' |
One arc with bands and the value in the centre. |
chord |
[from, to, value] flows, or matrix |
Ribbons between groups. See Chord layer. |
incident |
[{ from, to, label, keys }] |
Needs west-coast-charts/layers/quint loaded. A wedge across every ring for an incident window; see Quint. |
Common layer options: name, labels, thickness (px, '%' or fraction; rings without one share the
rest), gap, ringGap, padding, corner, colors, colorBy: 'spectrum', legend, legendTitle,
ringLabels, trackColor, startAngle, endAngle, min, max, link. Between the outermost layer
and the bars sits layerGap (10 px); the empty centre is hole ('auto', px, % or fraction).
Colours continue through one palette across the column series and all layers, so two layers never repeat a colour, and long legends stay distinguishable.
import WestCoastCharts from 'west-coast-charts'
import premium from 'west-coast-charts/premium'
WestCoastCharts.use(premium)
const hours = Array.from({ length: 24 }, (_, h) => `${h}:00`)
await new WestCoastCharts(document.querySelector('#chart'), {
chart: { type: 'radialColumn', height: 460 },
series: [{ name: 'Orders / h', data: hours.map((_, h) => 40 + ((h * 37) % 90)) }],
xaxis: { categories: hours },
plotOptions: {
radialColumn: {
layers: [
// innermost: share of orders per sales channel
{ type: 'donut', name: 'Channel', data: [['Web', 46], ['App', 38], ['Marketplace', 16]], legendTitle: 'Channels' },
// around it: last week vs this week per region
{
type: 'nestedDonut',
name: 'Regions',
labels: ['DACH', 'Nordics', 'Benelux'],
data: [
{ name: 'Last week', data: [52, 21, 27] },
{ name: 'This week', data: [49, 26, 25] },
],
legendTitle: 'Regions',
},
],
legend: { position: 'right' },
},
},
}).render()Linking layers to the bars
Layers and bars can mean independent things (a channel mix inside, hourly load outside) or connected
things (day parts inside, the hours they cover outside). plotOptions.radialColumn.link declares the
relation for all layers; a layer's own link overrides it, link: false switches it off.
mode |
A segment is linked to … |
|---|---|
'none' |
nothing |
'category' |
the categories with the same name as the segment or one of its children |
'angle' |
the bars whose slot centre lies inside the segment's angle |
'map' |
the categories you name: map: { segment: [categories] }, a function, or the datum's own link: [...] (indices or names) |
'auto' (default) |
datum link, else category names, else angle |
More link options: interaction: 'highlight' | 'filter' | 'both' (filter fades everything unlinked
on click), bidirectional (default true: hovering a bar lights the segments that link to it),
align (each segment spans exactly the angle of its linked bars), indicator (default true: a bracket
on the rim over each run of linked bars) and unit (how a category is called in announcements, for
example 'hours').
Hovering a segment lights it and its bars and dims the rest. Clicking makes the selection sticky; click
again, click empty space or press Escape to clear it. A segment with values: [one per category]
replaces the bars with its own values while selected, which is handy for "show me this channel's day".
import WestCoastCharts from 'west-coast-charts'
import premium from 'west-coast-charts/premium'
WestCoastCharts.use(premium)
const hours = Array.from({ length: 24 }, (_, h) => `${h}:00`)
const range = (a, b) => Array.from({ length: b - a }, (_, i) => a + i)
const chart = new WestCoastCharts(document.querySelector('#chart'), {
chart: { type: 'radialColumn', height: 440 },
series: [{ name: 'Support tickets', data: hours.map((_, h) => (h >= 8 && h < 18 ? 20 + (h % 5) * 6 : 4 + (h % 3))) }],
xaxis: { categories: hours },
plotOptions: {
radialColumn: {
link: { mode: 'map', interaction: 'both', align: true, unit: 'hours' },
layers: [{
type: 'donut',
name: 'Shift',
data: [
{ x: 'Night shift', y: 8, link: [...range(0, 6), ...range(22, 24)] },
{ x: 'Day shift', y: 10, link: range(6, 16) },
{ x: 'Late shift', y: 6, link: range(16, 22) },
],
}],
},
},
})
await chart.render()
// select a segment from code, read what it links to, clear again
const api = premium.api(chart)
api.select('Day shift')
console.log(api.selected(), api.links('Day shift'))
api.clearSelection()Semantic keys, ring labels and rim markers
keysname what a mark is ({ x: 'checkout', y: 12, keys: ['checkout'] }). Hovering a mark with keys lights the marks in other layers that share a key, rather than everything in the same angle. A sunburst parent carries the union of its children's keys.linkstill connects to the bars.ringLabels: trueon a layer writes curved labels along each segment, turned to read upright in the lower half, with ink chosen against the segment colour; labels that do not fit are dropped. Goal rings (labels on by default) too thin for a label inside, e.g. on a phone, get their names outside the stack instead: curved on a track in the free gap (at least 13 px), each at its ring's end with a leader line, pushed along where two would touch; a name that would move more than about 57° is left to the legend.legendTitlegroups a right-hand legend under one small header per layer.markersput a glyph just outside the bars at a slot:[{ at, label, kind, color, shape }]withat= slot index or category name andshape'dot'or'diamond'. The slot tooltip lists the markers; useful for deploys, incidents or releases.
import WestCoastCharts from 'west-coast-charts'
import premium from 'west-coast-charts/premium'
WestCoastCharts.use(premium)
const slots = Array.from({ length: 48 }, (_, i) => `${String(Math.floor(i / 2)).padStart(2, '0')}:${i % 2 ? '30' : '00'}`)
await new WestCoastCharts(document.querySelector('#chart'), {
chart: { type: 'radialColumn', height: 480 },
series: [{ name: 'Requests', data: slots.map((_, i) => 200 + ((i * 53) % 180)) }],
xaxis: { categories: slots },
plotOptions: {
radialColumn: {
categoryLabels: { formatter: (c, { index }) => (index % 6 === 0 ? c : '') }, // every 3 hours
markers: [
{ at: 19, label: 'Deploy api v2.14', kind: 'Deploy', shape: 'diamond' },
{ at: '15:30', label: 'Incident INC-311', kind: 'Incident', color: '#e11d48' },
],
layers: [{
type: 'sunburst',
name: 'Services',
ringLabels: true,
legendTitle: 'Services',
data: [
{ x: 'EU', children: [
{ x: 'checkout', y: 34, keys: ['checkout'] },
{ x: 'search', y: 22, keys: ['search'] },
] },
{ x: 'US', children: [
{ x: 'checkout', y: 28, keys: ['checkout'] },
{ x: 'search', y: 16, keys: ['search'] },
] },
],
}, {
type: 'donut',
name: 'Cost',
legendTitle: 'Cost per service',
data: [
{ x: 'checkout', y: 61, keys: ['checkout'] },
{ x: 'search', y: 39, keys: ['search'] },
],
}],
},
},
}).render()Hovering "checkout" in the sunburst now also lights "checkout" in the cost ring, because both carry the
key checkout.
Encoding extra metrics (colour, delta)
Besides the bar length, plotOptions.radialColumn.encode can bind more visual channels to their own
metrics. All of them are off until you configure them. Values come from the column datum (plain fields
or meta) or from values: [one per category].
| Channel | Option | Shows |
|---|---|---|
| Bar length | the series value, or encode.length: '<field>' |
the main quantity |
| Bar colour | encode.color: '<field>' or { field, name, unit, scale: 'sequential' | 'diverging', domain, mid, stops, reverse, legend } |
a second metric such as an error rate |
| Outer delta | encode.delta: { field, name, unit, style: 'band' | 'ticks' | 'bars', colors: { up, down }, max } |
change against a previous period |
| Angle | encode.angle: '<name>' |
names the dimension for the reading guide |
| Guide | encode.guide: true or { open: true | 'always' } |
a ? button that explains every channel |
A key under the ring shows the colour scale and the largest delta (legend: false per channel hides
it). Tooltips, keyboard labels, announcements and the data table list every channel by name.
import WestCoastCharts from 'west-coast-charts'
import premium from 'west-coast-charts/premium'
WestCoastCharts.use(premium)
const hours = Array.from({ length: 24 }, (_, h) => `${h}:00`)
const data = hours.map((x, h) => ({
x,
y: 300 + ((h * 71) % 260), // requests (bar length)
errorRate: +(0.4 + ((h * 13) % 30) / 10).toFixed(1), // % (bar colour)
vsLastWeek: ((h * 29) % 41) - 20, // % change (outer band)
}))
const chart = new WestCoastCharts(document.querySelector('#chart'), {
chart: { type: 'radialColumn', height: 460 },
series: [{ name: 'Requests', data }],
plotOptions: {
radialColumn: {
encode: {
angle: 'Hour of day',
color: { field: 'errorRate', name: 'Error rate', unit: '%', scale: 'sequential' },
delta: { field: 'vsLastWeek', name: 'vs last week', unit: '%', style: 'band' },
guide: true,
},
},
},
})
await chart.render()
const api = premium.api(chart)
console.log(api.describe('13:00')) // the slot as one line: hour, requests, error rate, change
console.log(api.channels()) // [channel, meaning] pairs, as listed in the guideGoal rings and the live gauge
Goals draw one ring per datum, innermost first, in the style of activity rings. A value past its
target (default 100) laps over itself in a shifted tone (maxLaps, default 2). Each ring may set its
own color, trackColor and label; labels are drawn along the ring, so keep them short.
Gauge shows one value on an arc and in the centre. With follow: '<series name>' it always shows the
newest value of that column series, marks the newest slot with a "now" tick (now: false hides it), and
the bars keep the history. bands colour the arc by value, formatter formats the centre.
import WestCoastCharts from 'west-coast-charts'
import premium from 'west-coast-charts/premium'
WestCoastCharts.use(premium)
const minutes = Array.from({ length: 30 }, (_, i) => `-${29 - i} min`)
let load = minutes.map((_, i) => 35 + ((i * 17) % 40))
const chart = new WestCoastCharts(document.querySelector('#chart'), {
chart: { type: 'radialColumn', height: 420 },
series: [{ name: 'CPU load', data: load }],
xaxis: { categories: minutes },
plotOptions: {
radialColumn: {
startAngle: -140,
endAngle: 140,
valueAxis: { min: 0, max: 100 },
layers: [
{
type: 'goals',
name: 'SLOs',
data: [
{ x: 'Availability', y: 99.2, target: 99.9, label: 'Avail 99.2%' },
{ x: 'Latency', y: 112, target: 100, label: 'p95 ok' },
],
},
{
type: 'gauge',
name: 'CPU now',
follow: 'CPU load',
formatter: (v) => `${Math.round(v)} %`,
bands: [{ to: 60, color: '#16a34a' }, { to: 80, color: '#d97706' }, { to: 100, color: '#dc2626' }],
},
],
},
},
})
await chart.render()
// streaming: shift the window and update; bars and gauge animate together
load = [...load.slice(1), 71]
await chart.updateSeries([{ name: 'CPU load', data: load }])Chord layer
A chord layer draws group arcs on a ring with ribbons between them, using the same layout as
chart.type: 'chord'. Put it innermost: layers inside a chord are drawn over its ribbons.
- Data: flows
[from, to, value]or{ from, to, value }, ormatrix(rows = from) withlabels. groups: [{ id, name, color, link }]fixes order, names, colours and the linked bars;sort: 'asc' | 'desc'orders by size;directed: truegives each end of a flow its own share.ringWidthsets the arc thickness;ribbon: { colorMode: 'source' | 'target' | 'gradient', opacity, padding }.- With
align: true(in the link options) every group spans exactly its linked bars. - Hovering a ribbon lights both groups and the bars of both ends; the tooltip shows both directions.
import WestCoastCharts from 'west-coast-charts'
import premium from 'west-coast-charts/premium'
WestCoastCharts.use(premium)
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
const categories = ['EU', 'US', 'APAC'].flatMap((r) => months.map((m) => `${r} ${m}`))
await new WestCoastCharts(document.querySelector('#chart'), {
chart: { type: 'radialColumn', height: 480 },
series: [{ name: 'Data transfer (TB)', data: categories.map((_, i) => 10 + ((i * 19) % 24)) }],
xaxis: { categories },
plotOptions: {
radialColumn: {
categoryLabels: { formatter: (c) => (c.endsWith('Jan') ? c.split(' ')[0] : '') },
link: { mode: 'map', align: true },
layers: [{
type: 'chord',
name: 'Replication',
ringWidth: 12,
groups: [
{ id: 'eu', name: 'EU', link: categories.filter((c) => c.startsWith('EU')) },
{ id: 'us', name: 'US', link: categories.filter((c) => c.startsWith('US')) },
{ id: 'apac', name: 'APAC', link: categories.filter((c) => c.startsWith('APAC')) },
],
data: [['eu', 'us', 42], ['us', 'eu', 31], ['us', 'apac', 18], ['apac', 'eu', 12]],
directed: true,
ribbon: { colorMode: 'gradient', opacity: 0.55 },
}],
},
},
}).render()Readable labels
Value-scale labels sit on top of the bars, often over several colours at once.
valueAxis.labels.readability decides how they stay legible; it defaults to 'auto'.
| Setting | Premium alone | With west-coast-charts/features/readability |
|---|---|---|
'auto' (default) |
a surface-coloured pill behind labels that sit on bars | picks per label: untouched off the bars, contrasting ink over one fill, a pill over mixed fills |
'pill' |
pill | pill with all options (pill: { fill, opacity, radius, padding, stroke, offset }) |
'hide' |
hides labels that sit on bars | same |
'halo', shadow, contrast, minRatio, each |
not available (ignored) | available |
'none' / false |
off | off |
Two more ways to keep the scale clear:
valueAxis.labels.angle: put the scale at a fixed angle (degrees, 0 = 12 o'clock) or'auto', the slot boundary with the shortest bars on both sides.pinAngle(defaulttrue) keeps the auto angle across updates so the scale does not wander.center.readability: the same settings for the centre value and label (default'none').
import WestCoastCharts from 'west-coast-charts'
import premium from 'west-coast-charts/premium'
import readability from 'west-coast-charts/features/readability'
WestCoastCharts.use(premium).use(readability)
const hours = Array.from({ length: 24 }, (_, h) => `${h}:00`)
await new WestCoastCharts(document.querySelector('#chart'), {
chart: { type: 'radialColumn', height: 400 },
series: [{ name: 'Throughput', data: hours.map((_, h) => 120 + ((h * 41) % 200)) }],
xaxis: { categories: hours },
plotOptions: {
radialColumn: {
valueAxis: {
labels: {
angle: 'auto',
readability: { mode: 'auto', contrast: true, minRatio: 4.5 },
},
},
center: { label: 'Throughput', readability: 'halo' },
},
},
}).render()Runtime API and events
premium.api(chart) returns the chart's runtime API (with script tags: WestCoastChartsPremium.api(chart)):
| Method | Does |
|---|---|
describe(category?) |
without an argument: the chart summary sentence; with a category: one line with every encoded channel of that slot (null when no encode is configured) |
dataTable() |
the rows of the accessible data table |
highlightCategory(name) / clearHighlight() |
light one slot from code |
toggle(id) |
hide or show a series ('s:<name>') or a segment ('c:<name>') |
layers() |
[{ index, type, name, rings, radii, link }] |
links(segment) |
the categories a segment links to |
select(nameOrPath) / selected() / clearSelection() |
sticky selection from code |
channels() |
the bound encoding channels as [channel, meaning] pairs |
Events (chart.events): dataPointSelection(e, chart, info) receives
{ seriesIndex, dataPointIndex, category, values } for a slot, and { layerIndex, layer, segment } for a
layer segment, plus linked and selected when links are active. legendClick(e, chart, { seriesName, hidden })
fires for legend toggles.
import WestCoastCharts from 'west-coast-charts'
import premium from 'west-coast-charts/premium'
WestCoastCharts.use(premium)
const chart = new WestCoastCharts(document.querySelector('#chart'), {
chart: {
type: 'radialColumn',
height: 360,
events: {
dataPointSelection: (e, ctx, info) => console.log('selected', info.category ?? info.segment?.name),
},
},
series: [{ name: 'Alerts', data: [3, 7, 2, 9, 4, 6, 1, 5] }],
xaxis: { categories: ['db', 'api', 'web', 'queue', 'cache', 'auth', 'cdn', 'search'] },
plotOptions: { radialColumn: { layers: [{ type: 'pie', name: 'Severity', data: [['critical', 4], ['warning', 21], ['info', 12]], link: false }] } },
})
await chart.render()
const api = premium.api(chart)
console.log(api.describe()) // summary sentence
console.log(api.layers().map((l) => `${l.type}:${l.name}`))
api.highlightCategory('api')
api.clearHighlight()Accessibility
- Keyboard: Tab focuses the chart; arrow keys move through the slots of the column ring and then the segments of each layer; PageDown and PageUp jump between rings; Enter selects; Escape clears.
- Each slot reads as one sentence including every encoded channel, for example "13:00 · 412 Requests · 3.4% Error rate · +12% vs last week".
- Linked segments announce what they cover ("18 of 72 time slots, peak 13:20").
- The hidden data table lists the categories with one column per series and channel, followed by one row per layer segment.
- Reduced-motion preferences skip the animations.
Performance tips
- Hit testing is geometric, so hundreds of thin bars need no per-element listeners. Above 150 bars the CSS highlight transitions are switched off automatically.
- Prefer
updateSeries/updateOptionsover destroying and re-creating: updates morph from what is on screen, even mid-animation. - For streaming, shift a fixed-length window as in the gauge example rather than growing the data.
The complete layout of every option is in the premium options reference.
WestCoast Charts 1.1.5 · generated from the library guide at commit 95f093ba. Code examples in the guide are executed by the library's docs check.