#[cfg(feature = "serde")]
pub mod config;
pub mod elements;
pub mod presets;
pub use elements::{ElementGeom, ElementLine, ElementRect, ElementText};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AxisSide {
Top,
Bottom,
Left,
Right,
}
#[derive(Clone, Debug, PartialEq)]
pub enum LegendPosition {
Right,
Left,
Top,
Bottom,
None,
Inside(f64, f64),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum LegendCorner {
TopLeft,
TopRight,
BottomLeft,
BottomRight,
}
impl LegendCorner {
pub const ALL: [LegendCorner; 4] = [
LegendCorner::BottomRight,
LegendCorner::BottomLeft,
LegendCorner::TopRight,
LegendCorner::TopLeft,
];
pub fn inside(self) -> LegendPosition {
match self {
LegendCorner::TopLeft => LegendPosition::Inside(0.0, 1.0),
LegendCorner::TopRight => LegendPosition::Inside(1.0, 1.0),
LegendCorner::BottomLeft => LegendPosition::Inside(0.0, 0.0),
LegendCorner::BottomRight => LegendPosition::Inside(1.0, 0.0),
}
}
pub fn name(self) -> &'static str {
match self {
LegendCorner::TopLeft => "top-left",
LegendCorner::TopRight => "top-right",
LegendCorner::BottomLeft => "bottom-left",
LegendCorner::BottomRight => "bottom-right",
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub enum TitlePosition {
#[default]
Panel,
Plot,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub enum TagPosition {
#[default]
TopLeft,
TopRight,
BottomLeft,
BottomRight,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum LegendDirection {
Vertical,
Horizontal,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum AxisLabelFit {
Clip,
Shift,
#[default]
Reserve,
}
#[derive(Clone, Debug)]
pub struct Margin {
pub top: f64,
pub right: f64,
pub bottom: f64,
pub left: f64,
}
impl Margin {
pub fn auto(values: &[f64]) -> Margin {
let (t, r, b, l) = match *values {
[] => (0.0, 0.0, 0.0, 0.0),
[a] => (a, a, a, a),
[v, h] => (v, h, v, h),
[t, h, b] => (t, h, b, h),
[t, r, b, l, ..] => (t, r, b, l),
};
Margin {
top: t,
right: r,
bottom: b,
left: l,
}
}
pub fn part(
self,
top: Option<f64>,
right: Option<f64>,
bottom: Option<f64>,
left: Option<f64>,
) -> Margin {
Margin {
top: top.unwrap_or(self.top),
right: right.unwrap_or(self.right),
bottom: bottom.unwrap_or(self.bottom),
left: left.unwrap_or(self.left),
}
}
}
impl Default for Margin {
fn default() -> Self {
Margin {
top: 10.0,
right: 10.0,
bottom: 10.0,
left: 10.0,
}
}
}
#[derive(Clone, Debug)]
pub struct Theme {
pub text: ElementText,
pub title: ElementText,
pub axis_text_x: ElementText,
pub axis_text_y: ElementText,
pub axis_title_x: ElementText,
pub axis_title_y: ElementText,
pub axis_line: ElementLine,
pub axis_ticks: ElementLine,
pub panel_background: ElementRect,
pub panel_grid_major: ElementLine,
pub panel_grid_minor: ElementLine,
pub plot_background: ElementRect,
pub legend_position: LegendPosition,
pub plot_margin: Margin,
pub subtitle: ElementText,
pub caption: ElementText,
pub legend_title: ElementText,
pub legend_text: ElementText,
pub strip_text: ElementText,
pub axis_line_x: Option<ElementLine>,
pub axis_line_y: Option<ElementLine>,
pub axis_ticks_x: Option<ElementLine>,
pub axis_ticks_y: Option<ElementLine>,
pub axis_line_x_top: Option<ElementLine>,
pub axis_line_x_bottom: Option<ElementLine>,
pub axis_line_y_left: Option<ElementLine>,
pub axis_line_y_right: Option<ElementLine>,
pub axis_ticks_x_top: Option<ElementLine>,
pub axis_ticks_x_bottom: Option<ElementLine>,
pub axis_ticks_y_left: Option<ElementLine>,
pub axis_ticks_y_right: Option<ElementLine>,
pub panel_grid_major_x: Option<ElementLine>,
pub panel_grid_major_y: Option<ElementLine>,
pub panel_grid_minor_x: Option<ElementLine>,
pub panel_grid_minor_y: Option<ElementLine>,
pub panel_border: ElementLine,
pub legend_background: ElementRect,
pub legend_key: ElementRect,
pub strip_background: ElementRect,
pub axis_ticks_length: f64,
pub axis_text_x_dodge: usize,
pub legend_key_width: f64,
pub legend_key_height: f64,
pub legend_spacing: f64,
pub legend_ticks_length: f64,
pub caption_position: Option<TitlePosition>,
pub legend_location_plot: bool,
pub axis_ticks_length_x: Option<f64>,
pub axis_ticks_length_y: Option<f64>,
pub legend_key_spacing_x: Option<f64>,
pub legend_key_spacing_y: Option<f64>,
pub legend_box_spacing: Option<f64>,
pub legend_box_just: f64,
pub rich_text: bool,
pub tag_location_panel: bool,
pub strip_placement_outside: bool,
pub strip_switch_pad: f64,
pub legend_margin: Margin,
pub panel_spacing: f64,
pub panel_spacing_x: Option<f64>,
pub panel_spacing_y: Option<f64>,
pub primary: Option<(u8, u8, u8)>,
pub aspect_ratio: Option<f64>,
pub panel_ontop: bool,
pub axis_minor_ticks: bool,
pub title_position: TitlePosition,
pub tag_position: TagPosition,
pub legend_direction: Option<LegendDirection>,
pub calendar_month_border: ElementLine,
pub legend_bar_border: Option<ElementLine>,
pub geom: ElementGeom,
pub axis_text_x_fit: AxisLabelFit,
pub axis_text_x_check_overlap: bool,
pub axis_text_x_top: Option<ElementText>,
pub axis_text_y_right: Option<ElementText>,
pub strip_text_x: Option<ElementText>,
pub strip_background_x: Option<ElementRect>,
pub strip_text_y: Option<ElementText>,
pub strip_background_y: Option<ElementRect>,
}
impl Theme {
pub fn legend_key_gap_y(&self) -> f64 {
self.legend_key_spacing_y.unwrap_or(0.0).max(0.0)
}
pub fn legend_margin_outer(&self) -> Margin {
let mut m = self.legend_margin.clone();
if let Some(g) = self.legend_box_spacing {
let g = g.max(0.0);
match self.legend_position {
LegendPosition::Right => m.left = g,
LegendPosition::Left => m.right = g,
LegendPosition::Top => m.bottom = g,
LegendPosition::Bottom => m.top = g,
LegendPosition::None | LegendPosition::Inside(..) => {}
}
}
m
}
pub fn ticks_len_x(&self) -> f64 {
self.axis_ticks_length_x.unwrap_or(self.axis_ticks_length)
}
pub fn ticks_len_y(&self) -> f64 {
self.axis_ticks_length_y.unwrap_or(self.axis_ticks_length)
}
pub fn hide_y_guides(&mut self) {
self.axis_text_y.visible = false;
self.axis_title_y.visible = false;
let hidden = |base: &ElementLine| ElementLine {
visible: false,
..base.clone()
};
self.axis_line_y = Some(hidden(self.axis_line_y.as_ref().unwrap_or(&self.axis_line)));
self.axis_ticks_y = Some(hidden(
self.axis_ticks_y.as_ref().unwrap_or(&self.axis_ticks),
));
self.panel_grid_major_y = Some(hidden(
self.panel_grid_major_y
.as_ref()
.unwrap_or(&self.panel_grid_major),
));
self.panel_grid_minor_y = Some(hidden(
self.panel_grid_minor_y
.as_ref()
.unwrap_or(&self.panel_grid_minor),
));
}
pub fn hide_position_guides(&mut self) {
for t in [
&mut self.axis_text_x,
&mut self.axis_text_y,
&mut self.axis_title_x,
&mut self.axis_title_y,
] {
t.visible = false;
}
for l in [
&mut self.axis_line,
&mut self.axis_ticks,
&mut self.panel_grid_major,
&mut self.panel_grid_minor,
] {
l.visible = false;
}
for l in [
&mut self.axis_line_x,
&mut self.axis_line_y,
&mut self.axis_ticks_x,
&mut self.axis_ticks_y,
&mut self.panel_grid_major_x,
&mut self.panel_grid_major_y,
&mut self.panel_grid_minor_x,
&mut self.panel_grid_minor_y,
]
.into_iter()
.flatten()
{
l.visible = false;
}
}
pub fn primary_or(&self, fallback: (u8, u8, u8)) -> (u8, u8, u8) {
self.primary.unwrap_or(fallback)
}
pub fn with_primary(mut self, color: (u8, u8, u8)) -> Self {
self.primary = Some(color);
self
}
pub fn set_geom(mut self, el: ElementGeom) -> Self {
self.geom = el;
self
}
pub fn with_ink_paper(mut self, ink: (u8, u8, u8), paper: (u8, u8, u8)) -> Self {
self.geom.ink = Some(ink);
self.geom.paper = Some(paper);
self
}
pub fn resolve_inheritance(&mut self) {
let root = self.text.clone();
let def = ElementText::default();
let children: [&mut ElementText; 10] = [
&mut self.title,
&mut self.subtitle,
&mut self.caption,
&mut self.axis_text_x,
&mut self.axis_text_y,
&mut self.axis_title_x,
&mut self.axis_title_y,
&mut self.legend_title,
&mut self.legend_text,
&mut self.strip_text,
];
for el in children {
if el.family == def.family {
el.family = root.family.clone();
}
if el.color == def.color {
el.color = root.color;
}
}
}
pub fn get_axis_line_x(&self) -> &ElementLine {
self.axis_line_x.as_ref().unwrap_or(&self.axis_line)
}
pub fn get_axis_line_y(&self) -> &ElementLine {
self.axis_line_y.as_ref().unwrap_or(&self.axis_line)
}
pub fn get_axis_ticks_x(&self) -> &ElementLine {
self.axis_ticks_x.as_ref().unwrap_or(&self.axis_ticks)
}
pub fn get_axis_ticks_y(&self) -> &ElementLine {
self.axis_ticks_y.as_ref().unwrap_or(&self.axis_ticks)
}
pub fn axis_line_at(&self, side: AxisSide) -> &ElementLine {
let own = match side {
AxisSide::Top => &self.axis_line_x_top,
AxisSide::Bottom => &self.axis_line_x_bottom,
AxisSide::Left => &self.axis_line_y_left,
AxisSide::Right => &self.axis_line_y_right,
};
own.as_ref().unwrap_or(match side {
AxisSide::Top | AxisSide::Bottom => self.get_axis_line_x(),
AxisSide::Left | AxisSide::Right => self.get_axis_line_y(),
})
}
pub fn axis_ticks_at(&self, side: AxisSide) -> &ElementLine {
let own = match side {
AxisSide::Top => &self.axis_ticks_x_top,
AxisSide::Bottom => &self.axis_ticks_x_bottom,
AxisSide::Left => &self.axis_ticks_y_left,
AxisSide::Right => &self.axis_ticks_y_right,
};
own.as_ref().unwrap_or(match side {
AxisSide::Top | AxisSide::Bottom => self.get_axis_ticks_x(),
AxisSide::Left | AxisSide::Right => self.get_axis_ticks_y(),
})
}
pub fn get_panel_grid_major_x(&self) -> &ElementLine {
self.panel_grid_major_x
.as_ref()
.unwrap_or(&self.panel_grid_major)
}
pub fn get_panel_grid_major_y(&self) -> &ElementLine {
self.panel_grid_major_y
.as_ref()
.unwrap_or(&self.panel_grid_major)
}
pub fn get_panel_grid_minor_x(&self) -> &ElementLine {
self.panel_grid_minor_x
.as_ref()
.unwrap_or(&self.panel_grid_minor)
}
pub fn get_panel_grid_minor_y(&self) -> &ElementLine {
self.panel_grid_minor_y
.as_ref()
.unwrap_or(&self.panel_grid_minor)
}
pub fn get_panel_spacing_x(&self) -> f64 {
self.panel_spacing_x.unwrap_or(self.panel_spacing)
}
pub fn get_panel_spacing_y(&self) -> f64 {
self.panel_spacing_y.unwrap_or(self.panel_spacing)
}
pub fn set_axis_text_x(mut self, el: ElementText) -> Self {
self.axis_text_x = el;
self
}
pub fn set_axis_text_y(mut self, el: ElementText) -> Self {
self.axis_text_y = el;
self
}
pub fn set_axis_title_x(mut self, el: ElementText) -> Self {
self.axis_title_x = el;
self
}
pub fn set_axis_title_y(mut self, el: ElementText) -> Self {
self.axis_title_y = el;
self
}
pub fn set_axis_line(mut self, el: ElementLine) -> Self {
self.axis_line = el;
self
}
pub fn set_axis_ticks(mut self, el: ElementLine) -> Self {
self.axis_ticks = el;
self
}
pub fn set_panel_background(mut self, el: ElementRect) -> Self {
self.panel_background = el;
self
}
pub fn set_panel_grid_major(mut self, el: ElementLine) -> Self {
self.panel_grid_major = el;
self
}
pub fn set_panel_grid_minor(mut self, el: ElementLine) -> Self {
self.panel_grid_minor = el;
self
}
pub fn set_plot_background(mut self, el: ElementRect) -> Self {
self.plot_background = el;
self
}
pub fn set_legend_position(mut self, pos: LegendPosition) -> Self {
self.legend_position = pos;
self
}
pub fn set_plot_margin(mut self, margin: Margin) -> Self {
self.plot_margin = margin;
self
}
pub fn set_title(mut self, el: ElementText) -> Self {
self.title = el;
self
}
pub fn set_text(mut self, el: ElementText) -> Self {
self.text = el;
self
}
pub fn set_subtitle(mut self, el: ElementText) -> Self {
self.subtitle = el;
self
}
pub fn set_caption(mut self, el: ElementText) -> Self {
self.caption = el;
self
}
pub fn set_legend_title(mut self, el: ElementText) -> Self {
self.legend_title = el;
self
}
pub fn set_legend_text(mut self, el: ElementText) -> Self {
self.legend_text = el;
self
}
pub fn set_strip_text(mut self, el: ElementText) -> Self {
self.strip_text = el;
self
}
pub fn set_axis_line_x(mut self, el: Option<ElementLine>) -> Self {
self.axis_line_x = el;
self
}
pub fn set_axis_line_y(mut self, el: Option<ElementLine>) -> Self {
self.axis_line_y = el;
self
}
pub fn set_axis_ticks_x(mut self, el: Option<ElementLine>) -> Self {
self.axis_ticks_x = el;
self
}
pub fn set_axis_ticks_y(mut self, el: Option<ElementLine>) -> Self {
self.axis_ticks_y = el;
self
}
pub fn set_panel_grid_major_x(mut self, el: Option<ElementLine>) -> Self {
self.panel_grid_major_x = el;
self
}
pub fn set_panel_grid_major_y(mut self, el: Option<ElementLine>) -> Self {
self.panel_grid_major_y = el;
self
}
pub fn set_panel_grid_minor_x(mut self, el: Option<ElementLine>) -> Self {
self.panel_grid_minor_x = el;
self
}
pub fn set_panel_grid_minor_y(mut self, el: Option<ElementLine>) -> Self {
self.panel_grid_minor_y = el;
self
}
pub fn set_panel_border(mut self, el: ElementLine) -> Self {
self.panel_border = el;
self
}
pub fn legend_bar_border_line(&self) -> ElementLine {
self.legend_bar_border.clone().unwrap_or(ElementLine {
color: self.legend_key.color.unwrap_or((50, 50, 50)),
width: 0.5,
visible: true,
linetype: crate::render::backend::Linetype::Solid,
})
}
pub fn set_calendar_month_border(mut self, el: ElementLine) -> Self {
self.calendar_month_border = el;
self
}
pub fn set_legend_bar_border(mut self, el: Option<ElementLine>) -> Self {
self.legend_bar_border = el;
self
}
pub fn set_legend_background(mut self, el: ElementRect) -> Self {
self.legend_background = el;
self
}
pub fn set_legend_key(mut self, el: ElementRect) -> Self {
self.legend_key = el;
self
}
pub fn set_strip_background(mut self, el: ElementRect) -> Self {
self.strip_background = el;
self
}
pub fn set_axis_ticks_length(mut self, val: f64) -> Self {
self.axis_ticks_length = val;
self
}
pub fn set_legend_key_width(mut self, val: f64) -> Self {
self.legend_key_width = val;
self
}
pub fn set_legend_key_height(mut self, val: f64) -> Self {
self.legend_key_height = val;
self
}
pub fn set_legend_spacing(mut self, val: f64) -> Self {
self.legend_spacing = val;
self
}
pub fn set_legend_margin(mut self, margin: Margin) -> Self {
self.legend_margin = margin;
self
}
pub fn set_panel_spacing(mut self, val: f64) -> Self {
self.panel_spacing = val;
self
}
pub fn set_panel_spacing_x(mut self, val: Option<f64>) -> Self {
self.panel_spacing_x = val;
self
}
pub fn set_panel_spacing_y(mut self, val: Option<f64>) -> Self {
self.panel_spacing_y = val;
self
}
pub fn update(mut self, upd: ThemeUpdate) -> Self {
if let Some(v) = upd.text {
self.text = v;
}
if let Some(v) = upd.title {
self.title = v;
}
if let Some(v) = upd.subtitle {
self.subtitle = v;
}
if let Some(v) = upd.caption {
self.caption = v;
}
if let Some(v) = upd.axis_text_x {
self.axis_text_x = v;
}
if let Some(v) = upd.axis_text_y {
self.axis_text_y = v;
}
if let Some(v) = upd.axis_title_x {
self.axis_title_x = v;
}
if let Some(v) = upd.axis_title_y {
self.axis_title_y = v;
}
if let Some(v) = upd.axis_line {
self.axis_line = v;
}
if let Some(v) = upd.axis_ticks {
self.axis_ticks = v;
}
if let Some(v) = upd.panel_background {
self.panel_background = v;
}
if let Some(v) = upd.panel_grid_major {
self.panel_grid_major = v;
}
if let Some(v) = upd.panel_grid_minor {
self.panel_grid_minor = v;
}
if let Some(v) = upd.panel_border {
self.panel_border = v;
}
if let Some(v) = upd.calendar_month_border {
self.calendar_month_border = v;
}
if let Some(v) = upd.legend_bar_border {
self.legend_bar_border = Some(v);
}
if let Some(v) = upd.plot_background {
self.plot_background = v;
}
if let Some(v) = upd.legend_position {
self.legend_position = v;
}
if let Some(v) = upd.legend_title {
self.legend_title = v;
}
if let Some(v) = upd.legend_text {
self.legend_text = v;
}
if let Some(v) = upd.legend_background {
self.legend_background = v;
}
if let Some(v) = upd.strip_text {
self.strip_text = v;
}
if let Some(v) = upd.strip_background {
self.strip_background = v;
}
if let Some(v) = upd.plot_margin {
self.plot_margin = v;
}
if let Some(v) = upd.geom {
self.geom = v;
}
for (v, slot) in [
(upd.axis_line_x, &mut self.axis_line_x),
(upd.axis_line_y, &mut self.axis_line_y),
(upd.axis_ticks_x, &mut self.axis_ticks_x),
(upd.axis_ticks_y, &mut self.axis_ticks_y),
] {
if v.is_some() {
*slot = v;
}
}
self
}
}
#[derive(Clone, Debug, Default)]
pub struct ThemeUpdate {
pub text: Option<ElementText>,
pub title: Option<ElementText>,
pub subtitle: Option<ElementText>,
pub caption: Option<ElementText>,
pub axis_text_x: Option<ElementText>,
pub axis_text_y: Option<ElementText>,
pub axis_title_x: Option<ElementText>,
pub axis_title_y: Option<ElementText>,
pub axis_line: Option<ElementLine>,
pub axis_ticks: Option<ElementLine>,
pub axis_line_x: Option<ElementLine>,
pub axis_line_y: Option<ElementLine>,
pub axis_ticks_x: Option<ElementLine>,
pub axis_ticks_y: Option<ElementLine>,
pub panel_background: Option<ElementRect>,
pub panel_grid_major: Option<ElementLine>,
pub panel_grid_minor: Option<ElementLine>,
pub panel_border: Option<ElementLine>,
pub calendar_month_border: Option<ElementLine>,
pub legend_bar_border: Option<ElementLine>,
pub plot_background: Option<ElementRect>,
pub legend_position: Option<LegendPosition>,
pub legend_title: Option<ElementText>,
pub legend_text: Option<ElementText>,
pub legend_background: Option<ElementRect>,
pub strip_text: Option<ElementText>,
pub strip_background: Option<ElementRect>,
pub plot_margin: Option<Margin>,
pub geom: Option<ElementGeom>,
}
impl Default for Theme {
fn default() -> Self {
presets::theme_gray()
}
}
pub fn theme_sub_axis_x(
text: Option<ElementText>,
title: Option<ElementText>,
line: Option<ElementLine>,
ticks: Option<ElementLine>,
) -> ThemeUpdate {
ThemeUpdate {
axis_text_x: text,
axis_title_x: title,
axis_line_x: line,
axis_ticks_x: ticks,
..Default::default()
}
}
pub fn theme_sub_axis_y(
text: Option<ElementText>,
title: Option<ElementText>,
line: Option<ElementLine>,
ticks: Option<ElementLine>,
) -> ThemeUpdate {
ThemeUpdate {
axis_text_y: text,
axis_title_y: title,
axis_line_y: line,
axis_ticks_y: ticks,
..Default::default()
}
}
pub fn theme_sub_legend(
title: Option<ElementText>,
text: Option<ElementText>,
background: Option<ElementRect>,
position: Option<LegendPosition>,
) -> ThemeUpdate {
ThemeUpdate {
legend_title: title,
legend_text: text,
legend_background: background,
legend_position: position,
..Default::default()
}
}
pub fn theme_sub_panel(
background: Option<ElementRect>,
border: Option<ElementLine>,
grid_major: Option<ElementLine>,
grid_minor: Option<ElementLine>,
) -> ThemeUpdate {
ThemeUpdate {
panel_background: background,
panel_border: border,
panel_grid_major: grid_major,
panel_grid_minor: grid_minor,
..Default::default()
}
}
pub fn theme_sub_strip(text: Option<ElementText>, background: Option<ElementRect>) -> ThemeUpdate {
ThemeUpdate {
strip_text: text,
strip_background: background,
..Default::default()
}
}
pub fn theme_sub_plot(
title: Option<ElementText>,
subtitle: Option<ElementText>,
caption: Option<ElementText>,
background: Option<ElementRect>,
margin: Option<Margin>,
) -> ThemeUpdate {
ThemeUpdate {
title,
subtitle,
caption,
plot_background: background,
plot_margin: margin,
..Default::default()
}
}