PolarAreaChart
Slices of equal angle whose length shows each value on rings of round ticks; hovering a slice or its legend picks it out.
Demo
| Value | |
|---|---|
| Mon | 132 |
| Tue | 158 |
| Wed | 141 |
| Thu | 187 |
| Fri | 224 |
| Sat | 96 |
| Sun | 78 |
| Value | |
|---|---|
| Outage | 4 |
| Slow | 11 |
| Data | 6 |
| Security | 2 |
| Billing | 8 |
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/polar_area_chart.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/polar_area_chart.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/polar_area_chart.qwe
let polar_area_chart_demo_grid = "display: grid; grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); gap: 32px 40px"view PolarAreaChartDemo() = <div style={polar_area_chart_demo_grid}> <PolarAreaChart label="Orders by weekday" show_label={true} parts={[ { label: "Mon", value: 132 }, { label: "Tue", value: 158 }, { label: "Wed", value: 141 }, { label: "Thu", value: 187 }, { label: "Fri", value: 224 }, { label: "Sat", value: 96 }, { label: "Sun", value: 78 }]}/> <PolarAreaChart label="Incidents by kind" show_label={true} labels={false} legend={true} size={220} parts={[ { label: "Outage", value: 4 }, { label: "Slow", value: 11 }, { label: "Data", value: 6 }, { label: "Security", value: 2 }, { label: "Billing", value: 8 }]}/> </div>Source
ui/polar_area_chart.qwe
// PolarAreaChart: parts as slices of equal angle round a centre, each reaching out// as far as its value on rings of round ticks: like a pie, but the slice's length,// not its angle, carries the number, so parts that are not shares of a whole (sales// by month, incidents by kind) can be compared. CSS only: hovering a slice or its// legend row picks it out, and screen readers get the numbers as a table.//// <PolarAreaChart label="Orders by weekday" parts={[// { label: "Mon", value: 132 }, { label: "Tue", value: 158 }, { label: "Wed", value: 141 }]}/>//// parts: a list of { label, value }, clockwise from the top (a null or a value below// 0 counts as 0); past eight, the eighth and the rest are counted together as// "Other". limit: the value at the rim, instead of the highest value rounded up.// labels: each slice's name outside the rim (true). legend: a list beside it with// each part's value (false). prefix and suffix: around every value shown;// decimals: digits after the point, always, instead of up to two as needed. size:// the chart's width in pixels (320; it shrinks to fit). label: what the chart shows;// it names it for screen readers and captions their table. show_label also writes it// above.import ui/theme.qwe use ui_clslet polar_area_chart_css = css`.ui-polarc { --ui-polarc-1: light-dark(#2a78d6, #3987e5); --ui-polarc-2: light-dark(#eb6834, #d95926); --ui-polarc-3: light-dark(#1baf7a, #199e70); --ui-polarc-4: light-dark(#eda100, #c98500); --ui-polarc-5: light-dark(#e87ba4, #d55181); --ui-polarc-6: light-dark(#008300, #008300); --ui-polarc-7: light-dark(#6250d6, #9085e9); --ui-polarc-8: light-dark(#e34948, #e66767); --ui-polarc-9: var(--ui-strong); display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 16px 28px; min-width: 0; margin: 0; font: 13px/1.4 var(--ui-font); color: var(--ui-fg); }.ui-polarc-title { flex-basis: 100%; margin: 0; font-size: 14px; font-weight: 600; }.ui-polarc-s1 { --c: var(--ui-polarc-1); } .ui-polarc-s2 { --c: var(--ui-polarc-2); } .ui-polarc-s3 { --c: var(--ui-polarc-3); }.ui-polarc-s4 { --c: var(--ui-polarc-4); } .ui-polarc-s5 { --c: var(--ui-polarc-5); } .ui-polarc-s6 { --c: var(--ui-polarc-6); }.ui-polarc-s7 { --c: var(--ui-polarc-7); } .ui-polarc-s8 { --c: var(--ui-polarc-8); }.ui-polarc-s9 { --c: var(--ui-polarc-9); }.ui-polarc-svg { display: block; max-width: 100%; height: auto; overflow: visible; }.ui-polarc-ring { fill: none; stroke: color-mix(in srgb, var(--ui-border) 80%, transparent); stroke-width: 1; }.ui-polarc-rim { stroke: var(--ui-strong); }.ui-polarc-slice { fill: var(--c); fill-opacity: .8; stroke: var(--ui-bg); stroke-width: 1.5; stroke-linejoin: round; }.ui-polarc-part { transition: opacity .15s ease; }.ui-polarc-part:hover .ui-polarc-slice { fill-opacity: 1; }.ui-polarc-name { fill: var(--ui-fg); font-size: 12px; }.ui-polarc-tick { fill: var(--ui-muted); font-size: 10px; font-variant-numeric: tabular-nums; paint-order: stroke; stroke: var(--ui-bg); stroke-width: 3px; stroke-linejoin: round; pointer-events: none; }.ui-polarc-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: 300px; margin: 0; padding: 0; list-style: none; }.ui-polarc-legend li { display: grid; grid-column: 1 / -1; grid-template-columns: subgrid; align-items: center; transition: opacity .15s ease; }.ui-polarc-key { width: 10px; height: 10px; border-radius: 3px; background: var(--c); }.ui-polarc-lname { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }.ui-polarc-val { text-align: right; font-weight: 600; font-variant-numeric: tabular-nums; }.ui-polarc:has(.ui-polarc-p1:hover) :is(.ui-polarc-part, .ui-polarc-legend li):not(.ui-polarc-p1),.ui-polarc:has(.ui-polarc-p2:hover) :is(.ui-polarc-part, .ui-polarc-legend li):not(.ui-polarc-p2),.ui-polarc:has(.ui-polarc-p3:hover) :is(.ui-polarc-part, .ui-polarc-legend li):not(.ui-polarc-p3),.ui-polarc:has(.ui-polarc-p4:hover) :is(.ui-polarc-part, .ui-polarc-legend li):not(.ui-polarc-p4),.ui-polarc:has(.ui-polarc-p5:hover) :is(.ui-polarc-part, .ui-polarc-legend li):not(.ui-polarc-p5),.ui-polarc:has(.ui-polarc-p6:hover) :is(.ui-polarc-part, .ui-polarc-legend li):not(.ui-polarc-p6),.ui-polarc:has(.ui-polarc-p7:hover) :is(.ui-polarc-part, .ui-polarc-legend li):not(.ui-polarc-p7),.ui-polarc:has(.ui-polarc-p8:hover) :is(.ui-polarc-part, .ui-polarc-legend li):not(.ui-polarc-p8) { opacity: .3; }@media (prefers-reduced-motion: reduce) { .ui-polarc-part, .ui-polarc-legend li { transition: none; } }`// n (a whole number, 0 or more) with at least `width` digits.fn polar_area_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 polar_area_chart_group(n: int) -> str = n < 1000 ? str(n) : polar_area_chart_group(floor(n / 1000)) + "," + polar_area_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 polar_area_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)}` : `.${polar_area_chart_pad(frac, digits)}`) `${v < 0 && units > 0 ? "-" : ""}${polar_area_chart_group(floor(units / scale))}${tail}`}// An axis tick, short: 1500 is "1.5k", 2000000 is "2M", 0.25 is "0.25".fn polar_area_chart_short(v: num) -> str { let a = abs(v) let unit = a >= 1000000000 ? 1000000000 : (a >= 1000000 ? 1000000 : (a >= 1000 ? 1000 : 1)) let tenths = round(a * 10 / unit) let mark = unit == 1000000000 ? "B" : (unit == 1000000 ? "M" : "k") unit == 1 ? polar_area_chart_num(v) : `${v < 0 ? "-" : ""}${polar_area_chart_group(floor(tenths / 10))}${tenths % 10 != 0 ? `.${tenths % 10}` : ""}${mark}`}// Rings of round values (1, 2, 2.5 or 5 x 10^n apart) out to just past `top`: of four// to six rings, the spacing whose outer ring comes closest to it.fn polar_area_chart_rings(top: num) -> list { let tries = for k in [4, 5, 6] => { let raw = top / k let mag = 1 while mag * 10 <= raw { mag = mag * 10 } while mag > raw { mag = mag / 10 } let r = raw / mag let step = (r <= 1 ? 1 : (r <= 2 ? 2 : (r <= 2.5 ? 2.5 : (r <= 5 ? 5 : 10)))) * mag let ring_count = ceil(top / step - 0.000001) for i in range(1, (ring_count < 1 ? 1 : ring_count) + 1) => i * step } sort_by(tries, (t) => t[-1])[0]}// A coordinate for SVG, to two decimals.fn polar_area_chart_len(n: num) -> str { let r = round(n * 100) / 100 r == floor(r) ? str(floor(r)) : str(r)}// The point at `turn` (0 to 1, clockwise from the top) on a circle of radius r.fn polar_area_chart_at(turn: num, r: num) -> list = [r * math.sin(turn * 2 * math.pi), -r * math.cos(turn * 2 * math.pi)]view PolarAreaChart(parts: list, limit: num? = null, labels: bool = true, legend: bool = false, prefix: str = "", suffix: str = "", decimals: int? = null, size: int = 320, label: str = "Polar area chart", show_label: bool = false, class: str? = null) { style global polar_area_chart_css let val = (p) => p.value != null && p.value > 0 ? p.value : 0 let shown = len(parts) > 8 ? take(parts, 7) + [{ label: "Other", value: sum(drop(parts, 7) | map(val)), other: true }] : parts let n = len(shown) let fmt = (v) => v == null ? "—" : `${v < 0 ? "-" : ""}${prefix}${polar_area_chart_num(abs(v), decimals)}${suffix}` let top0 = max([0] + (shown | map(val))) let rings0 = polar_area_chart_rings(limit != null && limit > 0 ? limit : (top0 == 0 ? 1 : top0)) let outer = limit != null && limit > 0 ? limit : rings0[-1] let rings = rings0 | filter((t) => t <= outer) let rim = 100 let r_of = (v) => rim * (v > outer ? outer : v) / outer let color = (i, p) => p.other == true ? 9 : i % 8 + 1 let wedge = (i, r) => { let [x1, y1] = polar_area_chart_at(i / n, r) let [x2, y2] = polar_area_chart_at((i + 1) / n, r) n == 1 ? `M0,${polar_area_chart_len(-r)} A${polar_area_chart_len(r)},${polar_area_chart_len(r)} 0 1 1 0,${polar_area_chart_len(r)} A${polar_area_chart_len(r)},${polar_area_chart_len(r)} 0 1 1 0,${polar_area_chart_len(-r)} Z` : `M0,0 L${polar_area_chart_len(x1)},${polar_area_chart_len(y1)} A${polar_area_chart_len(r)},${polar_area_chart_len(r)} 0 ${n == 2 ? 1 : 0} 1 ${polar_area_chart_len(x2)},${polar_area_chart_len(y2)} Z` } let side = labels ? 70 : 6 let tall = labels ? 22 : 6 <figure class={ui_cls(["ui-polarc", class])} aria-label={label}> {show_label && <figcaption.ui-polarc-title>{label}</figcaption>} <svg.ui-polarc-svg width={size} height={polar_area_chart_len(size * (rim + tall) / (rim + side))} aria-hidden="true" viewBox={`${-rim - side} ${-rim - tall} ${2 * (rim + side)} ${2 * (rim + tall)}`}> {for [i, p] in enumerate(shown) => <g class={`ui-polarc-part ui-polarc-p${i + 1} ui-polarc-s${color(i, p)}`}> {val(p) > 0 && <path.ui-polarc-slice d={wedge(i, r_of(val(p)))}> <title>{p.label}: {fmt(p.value)}</title> </path>} {labels && { let [x, y] = polar_area_chart_at((i + 0.5) / n, rim + 10) <text.ui-polarc-name x={polar_area_chart_len(x)} y={polar_area_chart_len(y)} dy={y < -rim * 0.9 ? "-0.1em" : (y > rim * 0.9 ? "0.8em" : "0.32em")} text-anchor={x > 5 ? "start" : (x < -5 ? "end" : "middle")}>{p.label}</text> }} </g>} {for t in rings => <circle class={ui_cls(["ui-polarc-ring", t == rings[-1] && "ui-polarc-rim"])} cx="0" cy="0" r={polar_area_chart_len(r_of(t))}/>} {for t in rings => <text.ui-polarc-tick x="3" y={polar_area_chart_len(-r_of(t))} dy="1em">{prefix}{polar_area_chart_short(t)}</text>} </svg> {legend && <ul.ui-polarc-legend aria-hidden="true"> {for [i, p] in enumerate(shown) => <li class={`ui-polarc-p${i + 1} ui-polarc-s${color(i, p)}`}> <span.ui-polarc-key></span><span.ui-polarc-lname>{p.label}</span><span.ui-polarc-val>{fmt(p.value)}</span> </li>} </ul>} <table.ui-sr> <caption>{label}</caption> <thead><tr><th scope="col"></th><th scope="col">Value</th></tr></thead> <tbody>{for p in shown => <tr><th scope="row">{p.label}</th><td>{fmt(p.value)}</td></tr>}</tbody> </table> </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 downloadsBarChartBars up or across, one series or several grouped or stacked, on round ticks, with values on hover and a table for screen readers.7 views · 7 downloadsBollingerChartPrices with Bollinger bands of a period and a number of deviations, closes outside them marked, as a line or candles.7 views · 5 downloadsBoxPlotQuartiles, median, whiskers and outliers for each group, worked out from the raw values, with the five numbers on hover.5 views · 7 downloadsBrushChartA large chart of a range with an overview below, where a window dragged or moved by keys picks the range shown.6 views · 5 downloadsBubbleChartPoints placed by two numbers and sized by a third, in coloured groups, labelled when big enough, with all three numbers on hover.7 views · 8 downloads
Thread
Thread 0 posts
On PolarAreaChart: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.