Charts
Data-driven charts from semantic <table> elements. No JavaScript. No external dependencies. data-chart on any table.
Charts in ASW are <table> elements with data-chart attributes. The table structure stays semantic — <caption>, <thead>, <tbody>, <th scope="row">. Data values are injected via style="--size: N" on <td> elements, where N is a 0–1 fraction of the maximum value.
style="--size: 0.8" is permitted on <td> elements inside charts. This is data injection, not presentation — it binds a numeric value to CSS. The --size custom property tells the stylesheet how tall or wide to render a bar. All other style="" attributes remain forbidden.
Bar chart
Horizontal bars. Best for: comparisons, rankings, categories with long labels.
| agentic-semantic-web | 12 |
|---|---|
| a-team | 9 |
| vigilio/vault | 7 |
| trentuna-web | 1 |
| commons | 1 |
Markup
<table data-chart="bar">
<caption>Issues by repository</caption>
<tbody>
<tr>
<th scope="row">agentic-semantic-web</th>
<td style="--size: 1.0" data-value="12">12</td>
</tr>
<tr>
<th scope="row">a-team</th>
<td style="--size: 0.75" data-value="9">9</td>
</tr>
<tr>
<th scope="row">vigilio/vault</th>
<td style="--size: 0.58" data-value="7">7</td>
</tr>
</tbody>
</table>
How --size works
Normalize your data: divide each value by the maximum. If the max is 12 and a value is 9, --size is calc(9/12) = 0.75. The bar fills 100% × --size of the available track width.
| Value | Max | --size |
|---|---|---|
| 12 | 12 | 1.0 |
| 9 | 12 | 0.75 (9/12) |
| 7 | 12 | 0.583 (7/12) |
| 1 | 12 | 0.083 (1/12) |
Column chart
Vertical bars. Best for: time series, sequential data, when x-axis labels are short.
| W1 | 42 |
|---|---|
| W2 | 49 |
| W3 | 36 |
| W4 | 54 |
| W5 | 45 |
| W6 | 60 |
| W7 | 53 |
| W8 | 56 |
Markup
<table data-chart="column">
<caption>Sessions per week</caption>
<tbody>
<tr><th scope="row">W1</th><td style="--size: 0.7">42</td></tr>
<tr><th scope="row">W2</th><td style="--size: 0.82">49</td></tr>
<tr><th scope="row">W3</th><td style="--size: 0.6">36</td></tr>
<tr><th scope="row">W4</th><td style="--size: 0.9">54</td></tr>
</tbody>
</table>
Custom height
Override --chart-height on the table element to change the chart area:
<table data-chart="column" style="--chart-height: 300px">
Area chart
Filled area from baseline. Best for: cumulative trends, showing volume over time.
| S1 | 40% |
|---|---|
| S2 | 55% |
| S3 | 35% |
| S4 | 70% |
| S5 | 65% |
| S6 | 85% |
| S7 | 50% |
| S8 | 90% |
Markup
<table data-chart="area">
<caption>Token budget consumption</caption>
<tbody>
<tr><th scope="row">S1</th><td style="--size: 0.4">40%</td></tr>
<tr><th scope="row">S2</th><td style="--size: 0.55">55%</td></tr>
<!-- ... -->
</tbody>
</table>
Line chart
Data points connected by a line. Best for: trends, changes over time where the line's slope matters more than individual values.
| W1 | 30 |
|---|---|
| W2 | 37 |
| W3 | 42 |
| W4 | 35 |
| W5 | 48 |
| W6 | 45 |
| W7 | 54 |
| W8 | 50 |
Markup
<table data-chart="line">
<caption>Open issues trend</caption>
<tbody>
<tr><th scope="row">W1</th><td style="--size: 0.5">30</td></tr>
<tr><th scope="row">W2</th><td style="--size: 0.62">37</td></tr>
<!-- ... -->
</tbody>
</table>
The line between data points is approximated via top-border styling. Each <td> has a border at its top (the data point height) and a dot marker. True curved interpolation requires SVG or JavaScript — this pattern is intentionally minimal.
Radial chart
Circular gauge showing a single value (0–1). Best for: token budgets, completion percentages, single-metric operational status.
| 72% |
| 58% |
| 91% |
Radial charts require a <span> inside the <td> for the value label. The ::before pseudo-element creates the donut-hole cutout, occupying the cell's first pseudo slot. A child <span> floats above via z-index: 1.
Markup
<table data-chart="radial" style="--size: 0.72">
<caption>Token budget</caption>
<tbody><tr><td><span>72%</span></td></tr></tbody>
</table>
Status variants
Add data-status="warning" (orange) or data-status="danger" (red) for threshold-based color changes:
<table data-chart="radial" style="--size: 0.78" data-status="warning">...</table>
<table data-chart="radial" style="--size: 0.91" data-status="danger">...</table>
How --size works
Arc = --size × 360deg. Normalize: --size = value / total. The gauge fills from the top, clockwise.
Burndown chart
Sprint burndown with CSS-generated ideal-velocity line. Best for: sprint tracking, issue completion over time. The diagonal overlay is pure CSS — no JavaScript.
| D1 | 19 |
|---|---|
| D2 | 17 |
| D3 | 15 |
| D4 | 12 |
| D5 | 11 |
| D6 | 8 |
| D7 | 7 |
| D8 | 4 |
| D9 | 2 |
| D10 | 1 |
Markup
<table data-chart="burndown">
<caption>Sprint burndown</caption>
<tbody>
<tr><th scope="row">D1</th><td style="--size: 0.95">19</td></tr>
<!-- --size = remaining / start_total -->
<tr><th scope="row">D10</th><td style="--size: 0.05">1</td></tr>
</tbody>
</table>
The ideal line
The blue diagonal is a linear-gradient on tbody::after. Represents constant burn velocity. Red bars above the gradient = behind pace. Below = ahead.
Query forgejo issues for a milestone, count remaining by day, normalize: --size = remaining / start. The table writes itself from Forgejo data. CSS draws the ideal line automatically.
Pie chart
Circular segments. Best for: proportions summing to 100%, where relative size matters.
| Philosophy 38% | Art 24% | Documentation 16% | Maintenance 14% | Communication 8% |
|---|
Markup
<table data-chart="pie"
style="--pie-segments: conic-gradient(
var(--chart-color-1) 0% 38%,
var(--chart-color-2) 38% 62%,
var(--chart-color-3) 62% 78%,
var(--chart-color-4) 78% 92%,
var(--chart-color-5) 92% 100%
)">
<caption>Time allocation by work type</caption>
<thead>
<tr>
<th>Philosophy 38%</th>
<th>Art 24%</th>
<th>Documentation 16%</th>
<th>Maintenance 14%</th>
<th>Communication 8%</th>
</tr>
</thead>
</table>
Pie segment math
Convert percentages to cumulative stops for conic-gradient:
| Segment | Value | Start% | End% |
|---|---|---|---|
| Philosophy | 38% | 0% | 38% |
| Art | 24% | 38% | 62% |
| Documentation | 16% | 62% | 78% |
| Maintenance | 14% | 78% | 92% |
| Communication | 8% | 92% | 100% |
Agent pattern: sum all values, convert each to a percentage, then compute cumulative offsets.
Attribute reference
| Attribute | Values | Purpose |
|---|---|---|
data-chart |
bar column area line pie radial burndown |
Chart type. Goes on the <table> element. |
data-chart-labels |
(boolean) | Show axis labels from <thead>. Presence = enabled. |
data-chart-spacing |
1–5 |
Gap between bars/columns. Default: 2. |
data-chart-stacked |
(boolean) | Stack multiple datasets in one bar/column. |
data-chart-reverse |
(boolean) | Reverse order of bars/columns. Bar: bottom-to-top. Column: right-to-left. |
data-status |
warning danger |
Color variant for radial charts. warning = orange. danger = red. |
style="--size: N" |
0–1 fraction | Data value on <td>. Normalized: max value = 1.0. |
style="--color: X" |
Any CSS color | Per-row color override on <tr>. Overrides the cycle. |
style="--chart-height: Npx" |
Any length | Override chart area height. Default: 200px. On <table>. |
style="--pie-segments: conic-gradient(...)" |
conic-gradient | Pie chart segments. Legal exception — data injection. |
data-value="N" |
Any string | Optional label shown after the bar. On <td>. |
Agent patterns
The normalization algorithm
To generate a bar or column chart, an agent follows this pattern:
# Given a dict of {label: value}:
data = {"repo-a": 12, "repo-b": 9, "repo-c": 7, "repo-d": 1}
max_val = max(data.values()) # 12
# For each row:
for label, value in data.items():
size = round(value / max_val, 3) # normalized 0–1
print(f'<tr><th scope="row">{label}</th><td style="--size: {size}">{value}</td></tr>')
# Output:
# <tr><th scope="row">repo-a</th><td style="--size: 1.0">12</td></tr>
# <tr><th scope="row">repo-b</th><td style="--size: 0.75">9</td></tr>
# <tr><th scope="row">repo-c</th><td style="--size: 0.583">7</td></tr>
# <tr><th scope="row">repo-d</th><td style="--size: 0.083">1</td></tr>
Choosing chart type
| Data shape | Best type |
|---|---|
| Rankings, comparisons, long labels | bar |
| Time series, short labels | column |
| Trend + volume over time | area |
| Trend shape, slope matters | line |
| Parts of a whole (sums to 100%) | pie |
Full example — Forgejo issue counts
<!-- Agent generates this from Forgejo API data -->
<table data-chart="bar">
<caption>Open issues by repo — 2026-04-03</caption>
<tbody>
<tr><th scope="row">agentic-semantic-web</th><td style="--size: 1.0" data-value="6">6</td></tr>
<tr><th scope="row">a-team</th><td style="--size: 0.833" data-value="5">5</td></tr>
<tr><th scope="row">vigilio/vault</th><td style="--size: 0.667" data-value="4">4</td></tr>
<tr><th scope="row">trentuna-web</th><td style="--size: 0.167" data-value="1">1</td></tr>
</tbody>
</table>