RadialBarChart
Bars bent into rings around a centre, each as long as its value against a limit, with a legend that picks a ring out on hover.
Demo
- Steps82%
- Exercise64%
- Stand100%
- Water45%
- Sleep91%
- Europe$48,200
- Americas$39,700
- Asia$27,150
- Africa$8,400
Get it
It needs theme.qwe, as every component does; or take the whole kit, quillo-ui.qwe. Each component brings its own styles to the pages that show it.
$ curl -O https://quillolang.org/components/ui/theme.qwe$ curl -O https://quillolang.org/components/ui/radial_bar_chart.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/radial_bar_chart.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/radial_bar_chart.qwe
view RadialBarChartDemo() = <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(380px, 1fr)); gap: 32px 40px"> <RadialBarChart label="Goals reached today" show_label={true} suffix="%" limit={100} size={210} items={[{ label: "Steps", value: 82 }, { label: "Exercise", value: 64 }, { label: "Stand", value: 100 }, { label: "Water", value: 45 }, { label: "Sleep", value: 91 }]}/> <RadialBarChart label="Sales by region" show_label={true} prefix="$" turn={360} size={200} items={[{ label: "Europe", value: 48200 }, { label: "Americas", value: 39700 }, { label: "Asia", value: 27150 }, { label: "Africa", value: 8400 }]}/> </div>Source
ui/radial_bar_chart.qwe
// RadialBarChart: a bar per item bent into a ring around a centre, rings inside// rings, each as long around as its value, with a legend of the values beside it// (goals reached, scores, shares of a target). CSS only: hovering a ring or its line// in the legend picks it out; screen readers get the values as a list.//// <RadialBarChart label="Goals reached" suffix="%" limit={100} items={[// { label: "Steps", value: 82 }, { label: "Sleep", value: 64 }, { label: "Water", value: 95 }]}/>// <RadialBarChart label="Sales by region" prefix="$" items={regions} turn={360} size={200}/>//// items: a list of { label, value }, the first on the outside ring; values below 0// count as 0. limit: the value a whole track stands for (by default the highest// value, which then fills its track); a value past it fills the track. turn: how far// round a full track goes, in degrees (270; 360 for whole circles). Each ring has// its label at its start, at the top, when the tracks leave room for it (300 or less). size: its width in pixels (240; it shrinks to// fit). legend: the list beside it, with each value (true; false keeps it for// screen readers only). prefix and suffix: around every value shown ("$", "%");// decimals: digits after the point, always, instead of up to two as needed. label:// what it shows, for screen readers; show_label also writes it above. Up to eight// items are picked out on hover; past that the colours repeat.import ui/theme.qwe use ui_clslet radial_bar_chart_css = css`.ui-radial { --ui-radial-1: light-dark(#2a78d6, #3987e5); --ui-radial-2: light-dark(#eb6834, #d95926); --ui-radial-3: light-dark(#1baf7a, #199e70); --ui-radial-4: light-dark(#eda100, #c98500); --ui-radial-5: light-dark(#e87ba4, #d55181); --ui-radial-6: light-dark(#008300, #008300); --ui-radial-7: light-dark(#6250d6, #9085e9); --ui-radial-8: light-dark(#e34948, #e66767); display: flex; flex-wrap: wrap; align-items: center; align-content: start; gap: 16px 28px; min-width: 0; margin: 0; font: 13px/1.4 var(--ui-font); color: var(--ui-fg); }.ui-radial-title { flex-basis: 100%; margin: 0; font-size: 14px; font-weight: 600; }.ui-radial-s1 { --c: var(--ui-radial-1); } .ui-radial-s2 { --c: var(--ui-radial-2); } .ui-radial-s3 { --c: var(--ui-radial-3); }.ui-radial-s4 { --c: var(--ui-radial-4); } .ui-radial-s5 { --c: var(--ui-radial-5); } .ui-radial-s6 { --c: var(--ui-radial-6); }.ui-radial-s7 { --c: var(--ui-radial-7); } .ui-radial-s8 { --c: var(--ui-radial-8); }.ui-radial-ring { flex: none; max-width: 100%; aspect-ratio: 1; }.ui-radial-ring svg { display: block; width: 100%; height: 100%; overflow: visible; }.ui-radial-track { fill: none; stroke: var(--ui-subtle); stroke-linecap: round; }.ui-radial-arc { fill: none; stroke: var(--c); stroke-linecap: round; transition: opacity .15s ease, stroke-width .15s ease; }.ui-radial-name { fill: var(--ui-muted); font-family: var(--ui-font); text-anchor: end; dominant-baseline: central; }.ui-radial-legend { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 6px 10px; align-items: center; flex: 1 1 180px; min-width: 0; max-width: 340px; margin: 0; padding: 0; list-style: none; }.ui-radial-legend li { display: grid; grid-column: 1 / -1; grid-template-columns: subgrid; align-items: center; transition: opacity .15s ease; }.ui-radial-key { width: 10px; height: 10px; border-radius: 3px; background: var(--c); }.ui-radial-lname { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }.ui-radial-val { text-align: right; font-weight: 600; font-variant-numeric: tabular-nums; }.ui-radial:has(.ui-radial-p1:hover) :is(.ui-radial-arc, .ui-radial-legend li):not(.ui-radial-p1),.ui-radial:has(.ui-radial-p2:hover) :is(.ui-radial-arc, .ui-radial-legend li):not(.ui-radial-p2),.ui-radial:has(.ui-radial-p3:hover) :is(.ui-radial-arc, .ui-radial-legend li):not(.ui-radial-p3),.ui-radial:has(.ui-radial-p4:hover) :is(.ui-radial-arc, .ui-radial-legend li):not(.ui-radial-p4),.ui-radial:has(.ui-radial-p5:hover) :is(.ui-radial-arc, .ui-radial-legend li):not(.ui-radial-p5),.ui-radial:has(.ui-radial-p6:hover) :is(.ui-radial-arc, .ui-radial-legend li):not(.ui-radial-p6),.ui-radial:has(.ui-radial-p7:hover) :is(.ui-radial-arc, .ui-radial-legend li):not(.ui-radial-p7),.ui-radial:has(.ui-radial-p8:hover) :is(.ui-radial-arc, .ui-radial-legend li):not(.ui-radial-p8) { opacity: .25; }@media (prefers-reduced-motion: reduce) { .ui-radial-arc, .ui-radial-legend li { transition: none; } }`// n (a whole number, 0 or more) with at least `width` digits.fn radial_bar_chart_pad(n: int, width: int) -> str { let s = str(n) len(s) < width ? repeat("0", width - len(s)) + s : s}// A whole number with commas: 1234567 is "1,234,567".fn radial_bar_chart_group(n: int) -> str = n < 1000 ? str(n) : radial_bar_chart_group(floor(n / 1000)) + "," + radial_bar_chart_pad(n % 1000, 3)// A value as it reads, with commas: `places` digits after the point, or up to two as// needed when that is null: 48210 is "48,210", 6350.5 is "6,350.5" ("6,350.50" with 2).// Worked out in whole numbers, so the server and the browser write the same text.fn radial_bar_chart_num(v: num, places: int? = null) -> str { let digits = places == null ? 2 : (places < 0 ? 0 : (places > 4 ? 4 : places)) let scale = [1, 10, 100, 1000, 10000][digits] let units = round(abs(v) * scale) let frac = units % scale let tail = digits == 0 || (places == null && frac == 0) ? "" : (places == null && frac % 10 == 0 ? `.${floor(frac / 10)}` : `.${radial_bar_chart_pad(frac, digits)}`) `${v < 0 && units > 0 ? "-" : ""}${radial_bar_chart_group(floor(units / scale))}${tail}`}// A coordinate or a percentage for CSS and SVG, to two decimals.fn radial_bar_chart_pct(n: num) -> str { let r = round(n * 100) / 100 r == floor(r) ? str(floor(r)) : str(r)}view RadialBarChart(items: list, limit: num? = null, turn: num = 270, size: int = 240, legend: bool = true, prefix: str = "", suffix: str = "", decimals: int? = null, label: str = "Radial bar chart", show_label: bool = false, class: str? = null) { style global radial_bar_chart_css let n = len(items) let fmt = (v) => `${v < 0 ? "-" : ""}${prefix}${radial_bar_chart_num(abs(v), decimals)}${suffix}` let val = (it) => it.value != null && it.value > 0 ? it.value : 0 let top0 = max([0] + (items | map(val))) let full = limit != null && limit > 0 ? limit : (top0 > 0 ? top0 : 1) let sweep = (turn < 30 ? 30 : (turn > 360 ? 360 : turn)) / 360 let outer = 92 let hole = 22 let band = n == 0 ? 0 : (outer - hole) / n let width = band * 0.7 > 16 ? 16 : band * 0.7 let r = (i) => outer - band * i - band / 2 let share = (it) => (val(it) > full ? 1 : val(it) / full) * sweep * 100 let text = band * 0.62 > 10 ? 10 : band * 0.62 <figure class={ui_cls(["ui-radial", class])} aria-label={label}> {show_label && <figcaption.ui-radial-title>{label}</figcaption>} <div.ui-radial-ring style={`width: ${size}px`} aria-hidden="true"> <svg viewBox="0 0 200 200"> <g transform="rotate(-90 100 100)"> {for [i, it] in enumerate(items) => [ <circle.ui-radial-track cx="100" cy="100" r={radial_bar_chart_pct(r(i))} pathLength="100" stroke-width={radial_bar_chart_pct(width)} stroke-dasharray={sweep >= 1 ? null : `${radial_bar_chart_pct(sweep * 100)} 100`}/>, val(it) > 0 && <circle class={`ui-radial-arc ui-radial-p${i + 1} ui-radial-s${i % 8 + 1}`} cx="100" cy="100" r={radial_bar_chart_pct(r(i))} pathLength="100" stroke-width={radial_bar_chart_pct(width)} stroke-dasharray={`${radial_bar_chart_pct(share(it))} 100`}> <title>{it.label}: {fmt(it.value ?? 0)}</title> </circle> ]} </g> {sweep <= 300 / 360 && (for [i, it] in enumerate(items) => <text.ui-radial-name x="94" y={radial_bar_chart_pct(100 - r(i))} style={`font-size: ${radial_bar_chart_pct(text)}px`}>{it.label}</text>)} </svg> </div> <ul class={ui_cls(["ui-radial-legend", !legend && "ui-sr"])}> {for [i, it] in enumerate(items) => <li class={`ui-radial-p${i + 1} ui-radial-s${i % 8 + 1}`}> <span.ui-radial-key aria-hidden="true"></span> <span.ui-radial-lname>{it.label}</span> <span.ui-radial-val>{fmt(it.value ?? 0)}</span> </li>} </ul> </figure>}Related
AreaChartFilled areas for one or more series, overlapping or stacked into a total, with a hover crosshair listing every value.6 views · 8 downloadsBulletChartMeasures as thin bars against a target mark over grey bands from poor to good, a row each, with the band named on hover.5 views · 7 downloadsComboChartBars on a left axis and lines on a right axis over the same labels, for two measures of different size, with every value on hover.6 views · 6 downloadsFunnelChartStages of a process narrowing like a funnel, each with its count and share of the first, and the share that went on between them.5 views · 7 downloadsGaugeChartA half-circle dial of good, warning and bad bands with a needle at the value, and the value and its band's name under it.6 views · 7 downloadsGroupedBarChartA bar per series side by side for each label, below zero too, with the group's values on hover and the bar under the pointer picked out.6 views · 4 downloads
Thread
Thread 0 posts
On RadialBarChart: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.