Charts¶
rich_ext::chart draws data with text:
Sparkline: one line, one cell per value.BarChart: a label, a bar and a value per row.Histogram: raw values counted into bins, drawn as bars.LineChart: one or more series as lines or scattered points, with axes and a legend.
And the pieces of a dashboard:
GaugeandBulletChart: a value against a range, with a target and threshold bands, on one line or the full width.Heatmap: a labelled grid of values drawn as shades, with a legend.StatusMatrix: rows and columns of states (pass, fail, skip, flaky or your own), each a symbol and a colour.KpiCard: a label, a value, its change, a sparkline and a status.Timeline: labelled ranges on a numeric or seconds scale, overlaps stacked, with milestones; a Gantt strip when every range has its own row.
Each chart is a renderable. It measures itself, so it works in a Table
cell, a Panel or a Live display. It never writes a line wider than the
width it is given: when space is short it drops labels, values and axes
before it drops data.
The examples come from
guide_charts.rs.
The module needs no feature:
cargo run -p rs-rich-ext --example guide_charts
cargo run -p rs-rich-ext --example charts # every chart on one screen
cargo run -p rs-rich-ext --example charts -- --ascii
cargo run -p rs-rich-ext --example kpi_dashboard # cards, a matrix and a timeline, live
The styles are theme keys (chart.*). Pass rich_ext::extended_theme() to
the console builder to get them, or define them in your own theme. Without
them the same defaults are used.
The same charts are in Python as rs_rich.chart,
and in the shell as rich chart.
Sparklines¶
Sparkline::new(values) draws each value as a cell whose height shows where
it falls between the smallest and largest value:
fn show_sparklines(console: &Console) {
console.print(&Sparkline::new(REQUESTS));
// Name the extremes and count the values over a threshold.
console.print(&Sparkline::new(REQUESTS).min_max(true).threshold(40.0));
// Two values per cell.
console.print(&Sparkline::new(REQUESTS).charset(Charset::Braille));
// Fewer cells than values: each cell is the mean of its bucket.
let narrow = Console::builder().width(8).color_system(None).build();
console.print(&Text::new(
narrow.render_to_string(&Sparkline::new(REQUESTS)),
));
}
- It is one cell per value wide. Given fewer cells, it resamples: the values are split into as many equal buckets as there are cells, and each cell shows the mean of its bucket's values.
.min_max(true)styles the lowest and highest cell (chart.min,chart.max) and writesmin 12 max 52after the line..threshold(40.0)styles the cells above 40 withchart.overand writes4 > 40(four values are above it).- The words are what make the extremes and the threshold readable without colour. They are the first thing left out when the line does not fit.
.range(min, max),.min(v)and.max(v)fix the scale; values outside it are clamped. NaN and infinite values are gaps.
Bars¶
BarChart draws a row per bar: the label, the bar and the value.
fn show_bars(console: &Console) {
let chart = BarChart::new()
.bar("api", 412.0)
.bar("web", 268.5)
.bar("worker", 97.0)
.push(Bar::new("cron", 12.0).style("chart.over"))
.bar_width(30);
console.print(&chart);
// Negative values extend left of zero.
console.print(
&BarChart::from_pairs([
("north", 18.0),
("south", -7.5),
("east", 4.0),
("west", -12.0),
])
.bar_width(30),
);
}
- Bars are measured from zero. The scale runs from the smallest to the
largest value and always includes zero;
.max(100.0)or.range(..)fix it. - Blocks give eighth-cell precision (
█▉▊▋▌▍▎▏). A value above zero always shows at least a sliver. - Negative values extend left of zero, at whole-cell precision, in
chart.negative. The written value keeps its minus sign, so the chart reads the same without colour. Bar::new(label, value).style(..)styles one bar;.style(..)on the chart styles them all. Both take a theme key or a style definition.- The bar is 40 cells by default (
.bar_width(n)). When the width is short the bar shrinks to 4 cells, then the values go, then the labels are cut with…. .show_values(false)leaves the values out;.format(ValueFormat::Fixed(1))writes them with one decimal instead of the compact form (1.2k,3.4M).
Vertical bars¶
.orientation(Orientation::Vertical) stands the bars up side by side, with
the labels underneath:
fn show_vertical(console: &Console) {
let week = BarChart::from_pairs([
("mon", 31.0),
("tue", 42.5),
("wed", 38.0),
("thu", 51.0),
("fri", 47.0),
("sat", 12.0),
("sun", 9.5),
])
.orientation(Orientation::Vertical)
.bar_width(8);
console.print(&week);
// Negative values hang below zero, their values under them.
console.print(
&BarChart::from_pairs([("q1", 18.0), ("q2", -7.5), ("q3", 4.0), ("q4", -12.0)])
.orientation(Orientation::Vertical)
.bar_width(6),
);
}
.bar_width(rows)is the tallest bar's height here, 8 rows by default.- Blocks give eighth-row precision (
▁▂▃▄▅▆▇█), Braille half rows (⣤⣿), ASCII#and a half.. A value above zero always shows at least a sliver. - Each bar gets a slot as wide as the widest label or value, with a cell between slots, and is up to 3 cells thick. Each value sits just above its bar.
- Negative values hang below the zero line at whole-row precision, their values under them.
- When the width is short the values go first, then the labels are cut (and left out below two cells), then the gaps between bars. Bars that still do not fit are left off the right.
Histogramtakes.orientation(..)too.
Histograms¶
Histogram::new(values).bins(n) counts raw values into n bins of equal
width and draws them as a BarChart:
fn show_histogram(console: &Console) {
console.print(&Histogram::new(latencies()).bins(8).bar_width(30));
}
- Each label is
[low, high), and the last[low, high], so every value falls in exactly one bin, the one its label names: a value on an edge counts in the bin that edge starts. - The edges are written exactly, never rounded: when the compact form would
round one (
1250as1.2k), they are all written in full ([1000, 1250))..edges()returns them without floating-point noise (0.3, not0.30000000000000004). - Without
.range(min, max), the bin width is rounded up to 1, 2, 2.5 or 5 times a power of ten and the first edge down to a multiple of it, so the labels are round numbers. The last bins may then be empty. - With
.range(min, max)the bins split that range exactly, and values outside it are not counted. NaN and infinite values are never counted. .counts()and.edges()return the numbers;.to_bar_chart()the chart.
Line and scatter charts¶
A LineChart plots one or more Series. Series::line(name, points) joins
its points, Series::scatter(name, points) does not, and
Series::from_values(name, values) puts the values at x = 0, 1, 2, …
let cpu = (0..60).map(|i| (i as f64, 50.0 + 30.0 * (i as f64 / 8.0).sin()));
let memory = (0..60).map(|i| (i as f64, 30.0 + i as f64 * 0.8));
let alerts = [(7.0, 92.0), (23.0, 88.0), (41.0, 95.0), (55.0, 90.0)];
LineChart::new()
.series(Series::line("cpu %", cpu))
.series(Series::line("memory %", memory))
.series(Series::scatter("alerts", alerts))
.y_range(0.0, 100.0)
.height(11)
.width(64)
- The plot is Braille by default: each cell holds 2×4 dots, so a chart 60 cells wide has 120 points across.
- Every row and column of the plot stands for one exact value, at its centre. Axis labels sit on evenly spaced rows and columns, and each label is the value of the row or column it is on; nothing is rounded to fit.
- Without
.y_range(..)and.x_range(..), each scale rounds out to a step of 1, 2, 2.5 or 5 times a power of ten that falls on whole rows or columns. The x scale may then run a little past the last point. - When the compact format cannot write those round values exactly, as with
years (
2015would read2.0k) or millisecond timestamps, the labels are all written in full (2015,2016, …) rather than spreading the data thinly over a coarser, writable step. - With a fixed range, a row or column gets a label only when the format
writes its value exactly, so a range must divide into the rows for every
label to show:
.y_range(0.0, 100.0)on 11 rows labels every 20, on 10 rows only0and100..y_format(..)and.x_format(..)write the labels. - It is
.height(rows)rows of plot, an axis line, a row of x labels and the legend. Without.height(..)it takes 6 to 10 rows, near 8, picking the height its y labels divide best. It fills the width it is given, or.width(n). When the width is short it drops the y labels, then the axes. - A NaN or infinite value breaks a line.
Without colour¶
Series are told apart by colour (chart.series.1 to chart.series.5) and
by marker: the legend shows each series' marker in its colour. Braille dots
have no shape, so when the console shows no colour and there is more than
one series, the chart plots at cell resolution with each series' marker
instead:
fn show_without_colour(console: &Console) {
// No colour: two or more series plot with markers, not Braille.
let plain = Console::builder().width(64).color_system(None).build();
console.print(&Text::new(plain.render_to_string(&traffic().height(6))));
}
"No colour" is the console's fidelity below Rich: no
colour system, NO_COLOR, or no_color(true). Series::marker('#') picks
a series' marker; .charset(Charset::Blocks) asks for markers even with
colour.
ASCII¶
Charset picks the glyphs: Blocks, Braille or Ascii. The default,
Auto, is blocks for sparklines and bars and Braille for line charts. An
ASCII-only console (its encoding is not UTF-8, or its fidelity is Ascii)
always gets Ascii, whatever the chart asks for, and its output holds no
character above U+007F:
fn show_ascii(console: &Console) {
// An ASCII-only console, or `.charset(Charset::Ascii)`.
let ascii = Console::builder()
.width(64)
.color_system(None)
.ascii_only(true)
.build();
let out = [
ascii.render_to_string(&Sparkline::new(REQUESTS).min_max(true)),
ascii.render_to_string(
&BarChart::new()
.bar("api", 412.0)
.bar("web", 268.5)
.bar_width(20),
),
ascii.render_to_string(&traffic().height(6)),
];
console.print(&Text::new(out.concat().trim_end()));
}
| Chart | ASCII |
|---|---|
Sparkline |
the ramp _.-:=+*#, lowest to highest |
BarChart, Histogram |
# for a cell, = for half a cell |
LineChart |
*, +, o, x, . per series; axes drawn with -, + and a vertical bar |
Gauge, BulletChart |
# and = for the bar, . and : for the bands, a vertical bar for the target |
Heatmap |
ten shades, .:-=+*#%@, and ? for a missing value |
StatusMatrix |
each state's ASCII symbol: + pass, X fail, - skip, ~ flaky |
KpiCard |
a border of +, - and vertical bars, ^ and v for the delta, the status's ASCII symbol |
Timeline |
# and = for ranges, * for milestones, ~ where a gap is cut |
Labels with characters above U+007F are written with ? in their place, as
fidelity::ascii_text does.
In tables and panels¶
A sparkline measures to one cell per value and a bar chart to its label, bar
and value, so they fit table columns. A line chart takes the width it is
given, or .width(n):
fn show_table(console: &Console) {
let mut table = Table::new().title("Services");
table.add_column("Service");
table.add_column("Requests, last 24 h");
table.add_column("p95 ms");
for (name, offset, p95) in [("api", 0, 412.0), ("web", 6, 268.0), ("worker", 12, 97.0)] {
let values = REQUESTS.iter().cycle().skip(offset).take(24).copied();
table.add_row_cells(vec![
Cell::Markup(name.into()),
Cell::Renderable(Arc::new(Sparkline::new(values))),
Cell::Renderable(Arc::new(
BarChart::new()
.bar("", p95)
.max(500.0)
.bar_width(12)
.format(ValueFormat::Fixed(0)),
)),
]);
}
console.print(&table);
let panel = Panel::new(Box::new(traffic().width(56).height(5)))
.title("cpu and memory")
.expand(false);
console.print(&panel);
}
Gauges and bullet charts¶
A Gauge shows one value against a range. .target(t) draws a │ across
the bar where the target is, and each Band is a threshold: the band runs
up to its value, the bar takes the band's style while the value is in it,
and the band's name is written after the value.
fn show_gauges(console: &Console) {
let cpu = Gauge::new("cpu", 72.0)
.range(0.0, 100.0)
.target(80.0)
.band(Band::new(60.0, "ok").style("chart.ok"))
.band(Band::new(85.0, "high").style("chart.warning"))
.band(Band::new(100.0, "critical").style("chart.critical"))
.unit("%");
// One line: label, bar, value and the band the value is in.
console.print(&cpu.clone().bar_width(24));
// Full width: a scale under the bar and a legend.
console.print(&cpu.full_width(true));
console.print(&Text::new(""));
// Several gauges with their columns aligned.
let quarter = BulletChart::new()
.gauge(Gauge::new("revenue", 270.0).range(0.0, 300.0).target(250.0))
.gauge(Gauge::new("profit", 22.5).range(0.0, 30.0).target(26.0))
.gauge(
Gauge::new("new customers", 1650.0)
.range(0.0, 2000.0)
.target(1800.0)
.band(Band::new(1400.0, "poor").style("chart.critical"))
.band(Band::new(1700.0, "fair").style("chart.warning"))
.band(Band::new(2000.0, "good").style("chart.ok")),
)
.bar_width(30);
console.print(&quarter);
}
- The bar fills with eighth-cell blocks (
█▉▊▋▌▍▎▏), half-cell Braille, or#and a half=in ASCII. The rest of the track is shaded by band, alternating░and▒(.and:), so each band's extent shows without colour. - The scale runs from 0 (or the smallest of the value, the target and the
bands) to the largest of them;
.range(min, max)fixes it. - The compact form is one line, with a bar of
.bar_width(n)cells (20 by default)..full_width(true)fills the width and adds a scale line (the bounds, the target and the band edges, each at its cell) and a legend (░ ok up to 60% │ target 80%). - Given less width the bar shrinks to 4 cells, then the band's name goes, then the value, then the label is cut.
BulletChartstacks gauges with their labels, bars, values and band names in aligned columns. Each keeps its own scale, target and bands.
Heatmaps¶
A Heatmap is a grid of values, a row per label and a column per header,
each cell a shade from the lowest value to the highest:
fn show_heatmap(console: &Console) {
let hours: Vec<String> = (0..24).map(|h| format!("{h:02}")).collect();
let mut map = Heatmap::new().columns(hours);
for (day, name) in ["mon", "tue", "wed", "thu", "fri", "sat", "sun"]
.into_iter()
.enumerate()
{
let weekend = if day >= 5 { 0.4 } else { 1.0 };
let load = (0..24).map(|h| {
let peak = (-((h as f64 - 14.0).powi(2)) / 30.0).exp();
(peak * 900.0 * weekend + 40.0 * day as f64).round()
});
map = map.row(name, load);
}
console.print(&map);
// In ASCII: ten shades, so it reads in black and white.
let ascii = Console::builder()
.width(60)
.color_system(None)
.ascii_only(true)
.build();
console.print(&Text::new(ascii.render_to_string(&map).trim_end()));
}
- Values are scaled into equal steps: five in blocks (
░▒▓█), ten in ASCII (.:-=+*#%@). The shade is the value, so the grid reads in black and white; with colour each step also takes achart.heat.Nstyle, cold to hot..range(min, max)fixes the scale. - A NaN or infinite value is drawn
·(?in ASCII), never as a low value, and the legend says so. - Each value is
.cell_width(n)cells wide (2 by default). A header is written at its column's first cell when it fits with a space after the one before, so with narrow cells every second or third header shows. - Given less width the cells narrow to one, then the row labels are cut, then neighbouring columns are merged, each showing the mean of its values, so every value still counts.
Status matrices¶
A StatusMatrix is rows and columns of states, such as a test suite
across platforms. Each state is a symbol and a colour, never colour
alone, and the legend counts the cells in each:
fn show_matrix(console: &Console) {
let matrix = StatusMatrix::new()
// Your own state: a name, a symbol, an ASCII symbol and a style.
.state(State::new("running", '◌', 'o', "chart.state.unknown"))
.columns(["linux", "macos", "windows", "wasm"])
.row("unit", ["pass", "pass", "pass", "pass"])
.row("integration", ["pass", "flaky", "fail", "skip"])
.row("docs", ["pass", "pass", "running", "skip"]);
console.print(&matrix);
}
| State | Symbol | ASCII | Theme key |
|---|---|---|---|
pass |
✓ |
+ |
chart.state.pass |
fail |
✗ |
X |
chart.state.fail |
skip |
○ |
- |
chart.state.skip |
flaky |
≈ |
~ |
chart.state.flaky |
State::new(name, symbol, ascii, style)adds a state, or replaces a built-in one with the same name. A symbol must take one cell; a wide one is replaced by its ASCII form.- A name with no state is drawn
?inchart.state.unknownand named in the legend as it is. A row shorter than the headers leaves its last cells blank. - Columns are as wide as their widest header. Given less width the headers are cut (and left out below three cells), then the row labels, then the gaps; columns that still do not fit are left off the right.
.counts()returns how many cells are in each state.
KPI cards¶
A KpiCard is one key number: a label, the value, its change, an optional
sparkline and an optional Status:
fn show_cards(console: &Console) {
let cards = vec![
KpiCard::new("Requests", 12_400.0)
.unit("/s")
.previous(11_430.0)
.caption("last week")
.trend(trend(0.0))
.status(Status::Ok),
KpiCard::new("Errors", 42.0)
.delta(-14.0)
.higher_is_better(false)
.caption("per hour")
.trend(trend(4.0))
.status(Status::Warning),
KpiCard::new("Latency", 412.0)
.unit(" ms")
.previous(260.0)
.higher_is_better(false)
.trend(trend(8.0))
.status(Status::Critical),
];
let cells = cards
.into_iter()
.map(|card| Cell::Renderable(Arc::new(card.width(24))))
.collect();
console.print(&Columns::from_cells(cells));
}
- The change is an arrow and a signed number:
▲ +8.49%,▼ -14,= 0(^,vand=in ASCII)..delta(d)gives an amount,.delta_percent(p)a percentage and.previous(v)the percentage change fromv. A change that is NaN or too large for anf64reads= -: no sign, and the flat arrow. It is styledchart.delta.goodorchart.delta.badby whether the change is for the better:.higher_is_better(false)for errors, latency and costs. The arrow and the sign carry the direction without colour. Status::Ok,Warning,CriticalandUnknownare a symbol and a word (✓ ok,! warning,✗ critical,? unknown) inchart.ok,chart.warning,chart.criticalorchart.unknown.- A card measures to its content, so cards sit side by side in
Columnsor a table..width(n)fixes its width, border included, and.expand(true)fills what it is given, as in aLayout. Cards with the same parts have the same height, so a row of them lines up. .border(false)leaves the border out; below 5 cells it goes anyway.
Timelines and Gantt strips¶
A Timeline puts labelled ranges on a numeric scale, such as the steps of
a build in seconds. Ranges with the same row label share a row:
fn show_timeline(console: &Console) {
let build = Timeline::new()
.span("fetch", 0.0, 4.0)
.span("compile", 4.0, 26.0)
// Overlapping ranges on one row are stacked.
.span("test", 12.0, 30.0)
.span("test", 20.0, 34.0)
.span("package", 34.0, 39.0)
.milestone("ship", 40.0)
.unit("s")
.width(60);
console.print(&build);
console.print(&Text::new(""));
// Ten minutes idle between two short bursts: the gap is cut out.
let jobs = Timeline::new()
.span("worker 1", 0.0, 3.0)
.span("worker 1", 3.0, 5.0)
.span("worker 2", 2.0, 6.0)
.span("worker 1", 600.0, 604.0)
.span("worker 2", 602.0, 610.0)
.milestone("deploy", 611.0)
.unit("s")
.width(60);
console.print(&jobs);
}
- Ranges on one row that overlap are stacked onto extra lines, so none
hides another. A range that starts where the one before it on its line
ends is drawn
▓instead of█(=instead of#), so both show. - A range covers every column whose centre value is inside it, and at least
one. Its length is written after it when there is room
(
.durations(false)leaves them out), so it reads without the axis. .milestone(label, at)marks a point with◆(*) on a line under the ranges, its label beside it.- The axis labels sit on evenly spaced columns and name each column's exact
value, as on a line chart;
.unit("s")writes a unit after every value. The scale is plain numbers: pass seconds (or any unit) asf64. - Compression: when the range is wider than the plot, so that the
shortest range would get less than a column, idle gaps are cut out when
they are longer than both four times the shortest range and three
columns' worth of the uncut scale. Each cut takes three columns with
≈(~) on the axis, and each stretch is labelled at its start and end..compress(false)keeps the scale linear;.range(min, max)fixes it. - It fills the width given, or
.width(n). Given less, the row labels are cut so the plot keeps 8 columns.
A dashboard¶
Cards, a matrix and a timeline in a Layout, rebuilt and passed to
Live::update on every tick, make a live dashboard. The
kpi_dashboard
example runs one; this is a frame of a smaller one:
fn dashboard() -> Layout {
let card = |c: KpiCard| Layout::with_renderable(Box::new(c.expand(true)));
let mut cards = Layout::new().size(6);
cards.split_row(vec![
card(
KpiCard::new("Requests", 1240.0)
.unit("/s")
.previous(1180.0)
.trend(trend(0.0)),
),
card(
KpiCard::new("Errors", 7.0)
.delta(2.0)
.higher_is_better(false)
.trend(trend(5.0))
.status(Status::Warning),
),
]);
let checks = StatusMatrix::new()
.columns(["eu", "us", "ap"])
.row("api", ["pass", "pass", "flaky"])
.row("worker", ["pass", "fail", "pass"]);
let deploy = Timeline::new()
.span("build", 0.0, 18.0)
.span("test", 12.0, 34.0)
.span("rollout", 36.0, 52.0)
.milestone("live", 60.0)
.unit("s");
let mut root = Layout::new();
root.split_column(vec![
cards,
Layout::with_renderable(Box::new(Panel::new(Box::new(checks)).title("checks"))).size(6),
Layout::with_renderable(Box::new(Panel::new(Box::new(deploy)).title("deploy"))),
]);
root
}
Cards take .expand(true) to fill their region. The layout fills the
console's height, so give Live a console with a fixed .height(n) or let
it take the terminal's.
From the shell: rich chart¶
rich chart draws data from a file, a URL or stdin with these charts, so a
shell pipeline can end in a picture. It reads CSV and TSV (the dialect
sniffed as rich --csv sniffs it), JSON (an array of records, of numbers or
of rows, or an object of columns), JSON Lines, or whitespace-separated
numbers.
rich chart sales.csv # every numeric column as a line
rich chart sales.csv --kind bar --x month --y api # a bar per row, labelled
rich chart sales.csv --kind heatmap # rows by series, as shades
seq 1 20 | rich chart --kind spark # numbers piped in
rich chart metrics.json --kind scatter --x t --y p50 --y p99
| Option | Meaning |
|---|---|
--kind spark\|bar\|line\|scatter\|heatmap |
What to draw; line by default |
--x COLUMN |
The positions (line, scatter: numbers) or labels (bar, heatmap). Default: the row number; for labels, the first column that is not all numbers |
--y COLUMN |
A series; repeat for several. Default: every numeric column but --x |
--width N |
The width to draw in |
Columns go by header or by 1-based number, so a CSV without a header works
too (its columns are 1, 2, ...). An empty cell or a JSON null is a gap.
A column that is not there, or a value that is not a number, is refused with
exit code 4 and a message naming the row (counted from 1, after the header)
and the column:
$ rich chart sales.csv --y mobile
rich: no column "mobile"; the columns are "month", "api", "web"
$ rich chart sales.csv --x month
rich: row 1, column "month": "Jan" is not a number (a line or scatter chart's --x is a position; --kind bar takes labels)
$ rich chart sales.csv --kind bar --y api --width 50
Jan ███████████████████████▌ 30
Feb █████████████████████████████████ 42
Mar ███████████████████████████▌ 35
Apr ████████████████████████████████████████ 51
May █████████████████████████████████████▋ 48
The recording shows it in a terminal.
The shared pieces¶
| Type | What it does |
|---|---|
Scale |
A linear scale. Scale::from_values skips NaN and infinities and never has an empty range: when every value is v the range is twice as wide as v with v in the middle (0..1 for 0), and no values gives 0..1. .nice(n) widens to round bounds; .ticks(n) gives about n round values inside it; .normalize(v) maps to 0..1 |
ValueFormat |
Compact (950, 0.25, 1.2k, 3.4M, 1.0e15 from a thousand trillion) or Fixed(decimals) (at most ValueFormat::MAX_DECIMALS, 17; more are capped) |
Status |
A health level for a KpiCard: a symbol, a word and a theme key |
State |
A state of a StatusMatrix cell: a name, a symbol, an ASCII symbol and a style |
Band, Span, Milestone |
A gauge's threshold band; a timeline's range and point |
Charset |
Auto, Blocks, Braille, Ascii |
DotCanvas |
The Braille canvas the line chart draws on: set dots, draw lines, read cells |
Theme keys:
| Key | Default | Used for |
|---|---|---|
chart.axis |
bright_black |
Axes and ticks |
chart.label, chart.value |
none | Labels, tick labels and values |
chart.bar |
cyan |
Bars |
chart.negative |
magenta |
Bars below zero |
chart.spark |
cyan |
Sparklines |
chart.over |
bold red |
Sparkline values above the threshold |
chart.min, chart.max |
blue, bold green |
Sparkline extremes |
chart.series.1 … .5 |
cyan, magenta, yellow, green, blue |
Series, by position (they cycle); timeline rows |
chart.track |
bright_black |
A gauge's track and band shades |
chart.target |
bold |
A gauge's target marker |
chart.ok, chart.warning, chart.critical, chart.unknown |
green, yellow, bold red, bright_black |
Status, and bands that name them |
chart.delta.good, chart.delta.bad, chart.delta.flat |
green, red, bright_black |
A KPI card's change |
chart.kpi.label, chart.kpi.value, chart.kpi.border |
none, bold, bright_black |
A KPI card |
chart.heat.1 … .5 |
blue, cyan, green, yellow, red |
Heatmap steps, cold to hot |
chart.state.pass, .fail, .skip, .flaky, .unknown |
green, bold red, bright_black, yellow, magenta |
Status matrix states |
chart.milestone |
bold yellow |
Timeline milestones |