Menu

Charts and strata

October 28, 2026 · Claude

After formulas and molecules, charts (#131). Like those, they're drawn when the site is built, as SVG in the page: no JavaScript, nothing loaded from elsewhere, sharp at any size, and in the theme's colors and font.

Downloads per episode

Downloads per episode First month Since then 1.7k 1.4k 1.1k 840 560 280 0 Ep. 1 Ep. 2 Ep. 3 Ep. 4 Ep. 5

In a Text block, that's a ```chart code block with the chart described in JSON:

```chart
{
  "title_text": "Downloads per episode",
  "x_axis_data": ["Ep. 1", "Ep. 2", "Ep. 3", "Ep. 4", "Ep. 5"],
  "series_list": [
    {"name": "First month", "data": [820, 932, 901, 1290, 1330]},
    {"name": "Since then", "data": [1020, 1132, 1201, 1490, 1630]}
  ]
}
```

Without "type" it's a bar chart. "type": "line", "pie", "scatter", "radar", "table", "heatmap", "histogram", "box_plot", "candlestick", "funnel", "waterfall", "gauge", "sankey", "treemap" and "polar_bar" give the others. The fields are those of charts-rs, which draws them, and they're much like the options of ECharts. A misspelt field doesn't go unnoticed: charts-rs checks the names, and the chart shows in red with the problem on hover.

A Harker diagram

In igneous petrology, the Harker diagram is the classic. Rock samples of one volcanic series are plotted with their silica content (SiO₂, in percent by weight) on the x-axis and another oxide on the y-axis. As a magma cools, magnesium- and calcium-rich minerals crystallize first and leave the melt richer in silica, so MgO and CaO fall from basalt to rhyolite:

Harker diagram A volcanic series from basalt to rhyolite MgO CaO SiO₂ (wt%) wt% 12 10 8 6 4 2 0 45 50 55 60 65 70 75 80

A scatter chart's data is a flat list of pairs: x, y, x, y and so on. The values here are made up, but typical of such a series.

A stratigraphic column

Geologists draw the layers of an outcrop or a borehole as a column: each layer as tall as it is thick, filled with the pattern for its rock. For that there's a ```strata block, which q.e.d. draws by itself. Here, simplified, the Triassic of the Northern Calcareous Alps, from the Hallstatt salt up to the Dachstein limestone:

Dachstein limestone (Norian–Rhaetian) (600 m)Hauptdolomit (Norian) (1500 m)Raibl beds (Carnian) (150 m)Wetterstein limestone (Ladinian) (800 m)Partnach beds (Ladinian) (100 m)Gutenstein and Reifling limestone (Anisian) (300 m)Reichenhall beds (Anisian) (50 m)Werfen beds (Lower Triassic) (400 m)Haselgebirge (Permian) (200 m)0 m1000200030004000LimestoneDolomiteSandstoneMarlEvaporite

Written as:

```strata
# youngest at the top
600 m | limestone | Dachstein limestone (Norian–Rhaetian)
1500 m | dolomite | Hauptdolomit (Norian)
150 m | sandstone | Raibl beds (Carnian)
…
```

One layer per line, youngest first: thickness, rock, and a name if you like. Lines with # are notes. The rocks q.e.d. knows are limestone, dolomite, marl, sandstone, siltstone, shale, claystone, conglomerate, breccia, coal, evaporite (gypsum, salt), sand, gravel, soil, granite, basalt and gneiss or schist, also by their German names (Kalk, Dolomit, Mergel, Sandstein …). As in sedimentary logs, fine rocks are drawn narrow and coarse ones wide. The scale counts from the base up, and a layer too thin for its name gets the name a little lower, with a line to where it belongs.

In Rust

One macro, seventeen chart types

charts-rs has a type per chart (BarChart, ScatterChart …), each with a from_json and an svg. svg in crates/qed-core/src/chart.rs picks one by the "type" field, and a small macro saves writing the same three lines seventeen times:

macro_rules! draw {
    ($chart:ty) => {
        <$chart>::from_json(json)
            .and_then(|chart| chart.svg())
            .map_err(|problem| problem.to_string())
    };
}
match kind {
    "bar" => draw!(BarChart),
    "scatter" => draw!(ScatterChart),
    // …
}

$chart:ty means the macro takes a type, and <$chart>::from_json calls a function of that type. The macro is defined inside the function, so it's known only there. Icons and a macro showed the first macro of the project; this one shows that a macro can take types, not just values.

A drawing as a String

strata.rs builds its SVG with write! into a String, the same write! that writes into files. That needs use std::fmt::Write as _;: the trait brings the method in, and as _ imports it without giving it a name, which would clash with std::io::Write. Writing into a String can't fail, so the result is dropped with let _ =.

The rocks are a table, ROCKS, a const array of structs with names, color, pattern and width. Finding the layers' rocks in it gives references (&'static Rock), and std::ptr::eq compares two such references by address: is it the same entry in the table? That's how the legend lists each rock once.

Colors from the theme, again

charts-rs draws text in #464646, grid lines in #E0E6F2 and puts a white background behind everything. The site's stylesheet replaces them with attribute selectors:

.chart [fill="#464646"] {
  fill: var(--color-base-content);
}
.chart [stroke="#E0E6F2"] {
  stroke: var(--color-base-300);
}

As with the molecules' label boxes, a CSS rule beats an SVG attribute. charts-rs's own font, Roboto, is replaced the same way by the site's font (font-family: inherit). The series keep their colors.