Look up
Furniture
Titles, labels, legends, colorbars, and what sheds first when the frame shrinks.
Furniture is everything on a chart that is not data: the title, the axis labels, the legend, the colorbar, the ticks and their labels, and the axes themselves. Malevich lays it out with one rule — furniture sheds before data — and one switch, axes(false), for the charts that want none.
The pieces#
The code that drew it
use malevich::scale::Colormap;
use malevich::{Cells, Line, Plot};
// Every piece of furniture at once: title, axis labels, a legend, a colorbar.
let (w, h) = (40, 12);
let field: Vec<f64> = (0..w * h).map(|i| ((i % w) as f64 * 0.3).sin() * ((i / w) as f64 * 0.5).cos()).collect();
let x: Vec<f64> = (0..40).map(f64::from).collect();
let ridge: Vec<f64> = x.iter().map(|x| 6.0 + 3.0 * (x * 0.2).sin()).collect();
Plot::new()
.layer(Cells::matrix(w, field).colormap(Colormap::CIVIDIS))
.layer(Line::xy(x, ridge).label("ridge").glow())
.colorbar()
.title("the furniture")
.x_label("column")
.y_label("row")| piece | comes from | shed order |
|---|---|---|
| title | Plot::title | second |
| axis labels | x_label, y_label | with the title row |
| legend | any layer with a label, or a color_by channel | first |
| colorbar | Plot::colorbar on a plot with a colormap | with the legend |
| context note | automatic, when labels leave out a base or a date | y note with the legend, x note with the title |
| tick labels | computed | last, by thinning |
The legend is built from layer labels in layer order. color_by adds one entry per category in first-appearance order, and Cells::classes adds swatches. A colorbar labels its ramp with the same exact-decimal formatter as the axes — decade ticks on a log colormap, band boundaries on a stepped one.
Shedding#
When the frame shrinks, the layout sheds furniture and does not fail: legend, then titles, then tick density. The data region is the last thing standing, because a small chart of the real numbers beats a complete frame around nothing. TERM=dumb at any width still gets a correct chart.
80×18
56×14
40×10
26×7
16×4
Rendering therefore never fails on frame grounds. Panics belong to construction, at the caller's line, on documented programmer invariants (unequal paired channels, a zero-column grid). A spec that arrives from data — deserialization, a config file — gets the checked twins, Plot::validate and Plot::try_render. They report the first problem as a typed error.
No axes at all#
axes(false) removes the axes, tick labels, and gutters, so the data region fills the frame. The sparkline preset is bars from zero with the axes off in a one-row frame. A waffle is class cells with the axes off. A thumbnail in a dashboard is anything with the axes off.
sparkline is the plot with axes(false), in a one-row frame.The code that drew it
use malevich::sparkline;
// No axes at all: bars from zero, one row tall in a one-row frame.
sparkline(vec![3.0, 5.0, 2.0, 8.0, 6.0, 7.0, 4.0, 9.0, 5.0, 3.0, 6.0, 8.0, 2.0, 4.0, 7.0, 9.0, 8.0, 5.0])axes(false) on a full plot lets the data region fill the frame.The code that drew it
use malevich::{Line, Plot};
// The same furniture switch on a full plot: the data region fills the frame.
let x: Vec<f64> = (0..200).map(|i| f64::from(i) * 0.05).collect();
let y: Vec<f64> = x.iter().map(|x| (x * 1.3).sin() * (x * 0.2).cos()).collect();
Plot::new().layer(Line::xy(x, y)).axes(false)The card#
The HTML and SVG cards add one more piece of furniture that a tty does not have: the card itself — a rounded rectangle in the theme's background and foreground. Theme::LIGHT selects the light card. Every other theme takes the dark one. The grid inside is the exact grid the terminal renderer would print.
What furniture will not do#
There is no legend placement option, no title alignment option, no gutter width option, no font. Each would be a knob on presentation that the frame already decides, and each would be one more thing the layout could not shed. Text the chart needs in a particular place is a Text mark at data coordinates. The rest is computed.