// Theming: a cohesive bundle of colors applied across a chart. Construct one
// with `Theme::light()` or `Theme::dark()`, optionally swap the series palette,
// and pass it to any chart via the `theme?` argument.
///|
/// Visual theme shared by every chart. `background` of `""` means transparent
/// (no background rectangle is drawn).
pub(all) struct Theme {
palette : Array[String] // series / category colors, cycled by index
background : String // page background, "" for transparent
grid : String // gridline color
axis : String // baseline and axis-tick label color
text : String // data value and category label color
title : String // chart title color
}
///|
let light_palette : Array[String] = [
"#4e79a7", "#f28e2b", "#e15759", "#76b7b2", "#59a14f", "#edc948", "#b07aa1", "#ff9da7",
"#9c755f", "#bab0ac",
]
///|
let dark_palette : Array[String] = [
"#6ea8e0", "#f6a04d", "#ef6f70", "#8fd4cd", "#7fc06a", "#f2d65c", "#c79bc0", "#ffb3bb",
"#bd9279", "#cfcac4",
]
///|
/// The default light theme: transparent background with dark text.
pub fn Theme::light() -> Theme {
{
palette: light_palette,
background: "",
grid: "#e6e6e6",
axis: "#999999",
text: "#333333",
title: "#222222",
}
}
///|
/// A dark theme: dark background, light text and a brighter palette.
pub fn Theme::dark() -> Theme {
{
palette: dark_palette,
background: "#1e1e2e",
grid: "#39394d",
axis: "#8888a0",
text: "#cdd0e0",
title: "#f0f0f8",
}
}
///|
/// Return a copy of this theme using a different series palette.
pub fn Theme::with_palette(self : Theme, palette : Array[String]) -> Theme {
{ ..self, palette, }
}
///|
/// Pick a series color by index, wrapping around when there are more series
/// than palette entries.
fn Theme::color_at(self : Theme, i : Int) -> String {
self.palette[i % self.palette.length()]
}
///|
/// A background rectangle covering the whole canvas, or `""` when the theme's
/// background is transparent.
fn background_rect(theme : Theme, width : Double, height : Double) -> String {
if theme.background == "" {
""
} else {
elem("rect", [
("x", "0"),
("y", "0"),
("width", num(width)),
("height", num(height)),
("fill", theme.background),
])
}
}
///|
/// A `` label using the charts' default sans-serif styling. Centralizing
/// this keeps font choices consistent and the chart code free of repetition.
fn label(
x : Double,
y : Double,
content : String,
size? : Int = 11,
anchor? : String = "middle",
weight? : String = "normal",
fill? : String = "#333333",
) -> String {
text(x, y, content, [
("text-anchor", anchor),
("font-family", "sans-serif"),
("font-size", size.to_string()),
("font-weight", weight),
("fill", fill),
])
}