mod facet_grid;
mod facet_wrap;
use crate::coordinate::Rect;
pub use facet_grid::FacetGridImpl;
pub use facet_wrap::FacetWrapImpl;
pub trait Facet: Send + Sync {
fn fields(&self) -> Vec<String>;
fn strategy(&self) -> FacetStrategy;
fn compute_panels(
&self,
factors: &[Vec<String>],
container: &Rect,
theme: &crate::theme::Theme,
) -> Vec<FacetPanel>;
}
#[derive(Debug, Clone)]
pub enum FacetSpec {
Wrap {
field: String,
columns: Option<usize>,
strategy: FacetStrategy,
},
Grid {
row_field: String,
col_field: String,
strategy: FacetStrategy,
},
}
impl FacetSpec {
pub fn wrap(field: &str) -> Self {
FacetSpec::Wrap {
field: field.to_string(),
columns: None,
strategy: FacetStrategy::Fixed,
}
}
pub fn grid(row_field: &str, col_field: &str) -> Self {
FacetSpec::Grid {
row_field: row_field.to_string(),
col_field: col_field.to_string(),
strategy: FacetStrategy::Fixed,
}
}
pub const fn with_columns(mut self, cols: usize) -> Self {
if let FacetSpec::Wrap {
ref mut columns, ..
} = self
{
*columns = Some(cols);
}
self
}
pub fn with_strategy(mut self, strategy: impl Into<FacetStrategy>) -> Self {
let strat = strategy.into();
match self {
FacetSpec::Wrap {
strategy: ref mut s,
..
}
| FacetSpec::Grid {
strategy: ref mut s,
..
} => {
*s = strat;
}
}
self
}
pub fn into_facet(self) -> Box<dyn Facet> {
match self {
FacetSpec::Wrap {
field,
columns,
strategy,
} => {
let wrap = FacetWrapImpl::new(field)
.with_columns(columns)
.with_strategy(strategy);
Box::new(wrap)
}
FacetSpec::Grid {
row_field,
col_field,
strategy,
} => {
let grid = FacetGridImpl::new(row_field, col_field).with_strategy(strategy);
Box::new(grid)
}
}
}
}
impl From<&str> for FacetSpec {
fn from(field: &str) -> Self {
FacetSpec::wrap(field)
}
}
impl From<(&str, usize)> for FacetSpec {
fn from((field, columns): (&str, usize)) -> Self {
FacetSpec::wrap(field).with_columns(columns)
}
}
impl From<(&str, &str)> for FacetSpec {
fn from((row_field, col_field): (&str, &str)) -> Self {
FacetSpec::grid(row_field, col_field)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum FacetStrategy {
Fixed, Free, FreeX, FreeY, }
impl FacetStrategy {
pub(crate) const fn axis_visibility(
self,
row: usize,
col: usize,
total_rows: usize,
is_last_in_column: bool,
) -> (bool, bool) {
let is_bottom = is_last_in_column || row + 1 == total_rows;
let is_left = col == 0;
match self {
Self::Fixed => (is_bottom, is_left),
Self::Free => (true, true),
Self::FreeX => (true, is_left),
Self::FreeY => (is_bottom, true),
}
}
}
impl From<&str> for FacetStrategy {
fn from(s: &str) -> Self {
match s.to_lowercase().as_str() {
"free" => FacetStrategy::Free,
"freex" | "free_x" => FacetStrategy::FreeX,
"freey" | "free_y" => FacetStrategy::FreeY,
"fixed" => FacetStrategy::Fixed,
_ => FacetStrategy::Fixed,
}
}
}
#[cfg(test)]
mod tests {
use super::FacetSpec;
use crate::coordinate::Rect;
use crate::theme::Theme;
#[test]
fn facet_panels_keep_equal_plot_dimensions() {
let container = Rect::new(0.0, 0.0, 1000.0, 800.0);
let theme = Theme::default();
let grid = FacetSpec::grid("row", "column").into_facet();
let grid_panels = grid.compute_panels(
&[
vec!["r1".to_string(), "r2".to_string()],
vec!["c1".to_string(), "c2".to_string()],
],
&container,
&theme,
);
let grid_size = (grid_panels[0].rect.width, grid_panels[0].rect.height);
assert!(
grid_panels
.iter()
.all(|panel| (panel.rect.width, panel.rect.height) == grid_size)
);
let wrap = FacetSpec::wrap("category").with_columns(2).into_facet();
let wrap_panels = wrap.compute_panels(
&[vec![
"a".to_string(),
"b".to_string(),
"c".to_string(),
"d".to_string(),
]],
&container,
&theme,
);
let wrap_size = (wrap_panels[0].rect.width, wrap_panels[0].rect.height);
assert!(
wrap_panels
.iter()
.all(|panel| (panel.rect.width, panel.rect.height) == wrap_size)
);
}
}
#[derive(Debug, Clone)]
pub struct FacetPanelInfo {
pub row: usize,
pub col: usize,
pub total_rows: usize,
pub total_cols: usize,
pub label: String,
pub row_label: String,
pub col_label: String,
pub facet_filter: Vec<(String, String)>,
pub show_x_axis: bool,
pub show_y_axis: bool,
}
#[derive(Clone)]
pub struct FacetPanel {
pub rect: Rect,
pub header_rect: Rect,
pub info: FacetPanelInfo,
}