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.

The one inline style exception

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.

Issues by repository
agentic-semantic-web12
a-team9
vigilio/vault7
trentuna-web1
commons1

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.

ValueMax--size
12121.0
9120.75 (9/12)
7120.583 (7/12)
1120.083 (1/12)

Column chart

Vertical bars. Best for: time series, sequential data, when x-axis labels are short.

Sessions per week (last 8 weeks)
W142
W249
W336
W454
W545
W660
W753
W856

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.

Token budget consumption over sessions
S140%
S255%
S335%
S470%
S565%
S685%
S750%
S890%

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.

Open issues trend (last 8 weeks)
W130
W237
W342
W435
W548
W645
W754
W850

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>
CSS-only line interpolation

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.

Token budget
72%
Issues resolved
58%
Disk usage
91%
The <span> inside <td>

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.

Sprint 3 — 20 issues, 10 days
D119
D217
D315
D412
D511
D68
D77
D84
D92
D101

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.

Agent-generated burndowns

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.

Time allocation by work type
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:

SegmentValueStart%End%
Philosophy38%0%38%
Art24%38%62%
Documentation16%62%78%
Maintenance14%78%92%
Communication8%92%100%

Agent pattern: sum all values, convert each to a percentage, then compute cumulative offsets.

Attribute reference

AttributeValuesPurpose
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 15 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 shapeBest type
Rankings, comparisons, long labelsbar
Time series, short labelscolumn
Trend + volume over timearea
Trend shape, slope mattersline
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>