use crate::subplot::{Subplot, SubplotDescriptor};
use crate::PltError;
#[cfg(doc)]
use crate::figure::Figure;
pub trait Layout<'a> {
fn subplots(self) -> Vec<(Subplot<'a>, FractionalArea)>;
}
pub struct SingleLayout<'a> {
subplot: Subplot<'a>,
}
impl<'a> SingleLayout<'a> {
pub fn new(subplot: Subplot<'a>) -> Self {
Self { subplot }
}
}
impl<'a> Layout<'a> for SingleLayout<'a> {
fn subplots(self) -> Vec<(Subplot<'a>, FractionalArea)> {
vec![(
self.subplot,
FractionalArea { xmin: 0.0, xmax: 1.0, ymin: 0.0, ymax: 1.0},
)]
}
}
pub struct GridLayout<'a> {
subplots: ndarray::Array2<Subplot<'a>>,
areas: ndarray::Array2<FractionalArea>,
mask: ndarray::Array2<bool>,
}
impl<'a> GridLayout<'a> {
pub fn new(nrows: usize, ncols: usize) -> Self {
let xextent = 1.0 / ncols as f64;
let yextent = 1.0 / nrows as f64;
let areas = (0..(nrows * ncols))
.map(|index| {
let row = index / ncols;
let col = index % ncols;
let xmin = xextent * col as f64;
let xmax = xmin as f64 + xextent;
let ymin = yextent * (nrows - 1 - row) as f64;
let ymax = ymin as f64 + yextent;
FractionalArea { xmin, xmax, ymin, ymax }
})
.collect::<ndarray::Array1<_>>();
let areas = areas.into_shape((nrows, ncols)).unwrap();
Self {
subplots: ndarray::Array2::from_elem(
(nrows, ncols),
Subplot::new(&SubplotDescriptor::default()),
),
areas,
mask: ndarray::Array2::from_elem((nrows, ncols), false),
}
}
pub fn from_array<A: Into<ndarray::Array2<Option<Subplot<'a>>>>>(subplots: A) -> Self {
let subplots = subplots.into();
let nrows = subplots.nrows();
let ncols = subplots.ncols();
let xextent = 1.0 / ncols as f64;
let yextent = 1.0 / nrows as f64;
let areas = (0..(nrows * ncols))
.map(|index| {
let row = index / ncols;
let col = index % ncols;
let xmin = xextent * col as f64;
let xmax = xmin as f64 + xextent;
let ymin = yextent * (nrows - 1 - row) as f64;
let ymax = ymin as f64 + yextent;
FractionalArea { xmin, xmax, ymin, ymax }
})
.collect::<ndarray::Array1<_>>();
let areas = areas.into_shape((nrows, ncols)).unwrap();
let mask = subplots.map(|subplot| subplot.is_some());
let subplots = subplots.mapv(|subplot| {
subplot.unwrap_or_else(|| Subplot::new(&SubplotDescriptor::default()))
});
Self {
subplots,
areas,
mask,
}
}
pub fn insert(
&mut self,
(row, col): (usize, usize),
subplot: Subplot<'a>,
) -> Result<(), PltError> {
if (row + 1) > self.subplots.nrows() {
return Err(PltError::InvalidRow { row, nrows: self.subplots.nrows() });
}
if (col + 1) > self.subplots.ncols() {
return Err(PltError::InvalidColumn { col, ncols: self.subplots.ncols() });
}
self.subplots[[row, col]] = subplot;
self.mask[[row, col]] = true;
Ok(())
}
}
impl<'a> Layout<'a> for GridLayout<'a> {
fn subplots(self) -> Vec<(Subplot<'a>, FractionalArea)> {
Iterator::zip(
self.subplots.indexed_iter().filter_map(|(index, subplot)|
if self.mask[index] { Some(subplot) } else { None }
).cloned(),
self.areas.indexed_iter().filter_map(|(index, area)|
if self.mask[index] { Some(area) } else { None }
).cloned(),
).collect()
}
}
#[derive(Copy, Clone, Debug)]
pub struct FractionalArea {
pub xmin: f64,
pub xmax: f64,
pub ymin: f64,
pub ymax: f64,
}
impl FractionalArea {
pub(crate) fn to_area(self, size: draw::Size) -> draw::Area {
draw::Area {
xmin: (self.xmin * size.width as f64).ceil() as u32,
xmax: (self.xmax * size.width as f64).floor() as u32,
ymin: (self.ymin * size.height as f64).ceil() as u32,
ymax: (self.ymax * size.height as f64).floor() as u32,
}
}
pub(crate) fn valid(&self) -> bool {
self.xmin >= 0.0 && self.xmin <= 1.0
&& self.xmax >= 0.0 && self.xmax <= 1.0
&& self.ymin >= 0.0 && self.ymin <= 1.0
&& self.ymax >= 0.0 && self.ymax <= 1.0
&& self.xmin < self.xmax
&& self.ymin < self.ymax
}
}