use ironlab_ir::{
Artist, Axes, Axis, ColorSpec, ColormapName, Contour, ContourPlacement, Dimension, IrError,
Legend, LegendLocation, Limits, Line, NdArray, NodeId, Projection, Quiver, Scale, Scatter,
Surface, Text, View3d,
};
use crate::artists::{ContourMut, LineMut, QuiverMut, ScatterMut, SurfaceMut};
use crate::grid::{GridCoords, matrix_array, store_grid};
use crate::matrix::Matrix;
#[derive(Debug)]
pub struct AxesMut<'f> {
pub(crate) fig: &'f mut ironlab_ir::Figure,
pub(crate) id: NodeId,
}
impl<'f> AxesMut<'f> {
pub(crate) fn new(fig: &'f mut ironlab_ir::Figure, id: NodeId) -> Self {
Self { fig, id }
}
}
impl AxesMut<'_> {
#[must_use]
pub fn id(&self) -> NodeId {
self.id
}
pub fn plot(&mut self, x: impl AsRef<[f64]>, y: impl AsRef<[f64]>) -> LineMut<'_> {
self.add_line(x.as_ref(), y.as_ref(), None)
}
pub fn plot3(
&mut self,
x: impl AsRef<[f64]>,
y: impl AsRef<[f64]>,
z: impl AsRef<[f64]>,
) -> LineMut<'_> {
self.make_3d();
self.add_line(x.as_ref(), y.as_ref(), Some(z.as_ref()))
}
pub fn loglog(&mut self, x: impl AsRef<[f64]>, y: impl AsRef<[f64]>) -> LineMut<'_> {
self.set_xy_scales(Scale::Log, Scale::Log);
self.add_line(x.as_ref(), y.as_ref(), None)
}
pub fn semilogx(&mut self, x: impl AsRef<[f64]>, y: impl AsRef<[f64]>) -> LineMut<'_> {
self.set_xy_scales(Scale::Log, Scale::Linear);
self.add_line(x.as_ref(), y.as_ref(), None)
}
pub fn semilogy(&mut self, x: impl AsRef<[f64]>, y: impl AsRef<[f64]>) -> LineMut<'_> {
self.set_xy_scales(Scale::Linear, Scale::Log);
self.add_line(x.as_ref(), y.as_ref(), None)
}
pub fn scatter(&mut self, x: impl AsRef<[f64]>, y: impl AsRef<[f64]>) -> ScatterMut<'_> {
self.add_scatter(x.as_ref(), y.as_ref(), None)
}
pub fn scatter3(
&mut self,
x: impl AsRef<[f64]>,
y: impl AsRef<[f64]>,
z: impl AsRef<[f64]>,
) -> ScatterMut<'_> {
self.make_3d();
self.add_scatter(x.as_ref(), y.as_ref(), Some(z.as_ref()))
}
pub fn contour(
&mut self,
x: impl Into<GridCoords>,
y: impl Into<GridCoords>,
z: &Matrix,
) -> ContourMut<'_> {
let placement = ContourPlacement::default();
self.add_contour(x.into(), y.into(), z, false, placement)
}
pub fn contourf(
&mut self,
x: impl Into<GridCoords>,
y: impl Into<GridCoords>,
z: &Matrix,
) -> ContourMut<'_> {
let placement = ContourPlacement::default();
self.add_contour(x.into(), y.into(), z, true, placement)
}
pub fn contour3(
&mut self,
x: impl Into<GridCoords>,
y: impl Into<GridCoords>,
z: &Matrix,
) -> ContourMut<'_> {
self.make_3d();
self.add_contour(x.into(), y.into(), z, false, ContourPlacement::AtLevel)
}
pub fn quiver(
&mut self,
x: impl AsRef<[f64]>,
y: impl AsRef<[f64]>,
u: impl AsRef<[f64]>,
v: impl AsRef<[f64]>,
) -> QuiverMut<'_> {
let x = self.store_vector(x.as_ref());
let y = self.store_vector(y.as_ref());
let u = self.store_vector(u.as_ref());
let v = self.store_vector(v.as_ref());
let id = self.push_artist(|id| {
Artist::Quiver(Quiver {
id,
x,
y,
u,
v,
..Quiver::default()
})
});
QuiverMut::new(self.fig, id)
}
pub fn quiver3(
&mut self,
x: impl AsRef<[f64]>,
y: impl AsRef<[f64]>,
z: impl AsRef<[f64]>,
u: impl AsRef<[f64]>,
v: impl AsRef<[f64]>,
w: impl AsRef<[f64]>,
) -> QuiverMut<'_> {
self.make_3d();
let x = self.store_vector(x.as_ref());
let y = self.store_vector(y.as_ref());
let z = self.store_vector(z.as_ref());
let u = self.store_vector(u.as_ref());
let v = self.store_vector(v.as_ref());
let w = self.store_vector(w.as_ref());
let id = self.push_artist(|id| {
Artist::Quiver(Quiver {
id,
x,
y,
z: Some(z),
u,
v,
w: Some(w),
..Quiver::default()
})
});
QuiverMut::new(self.fig, id)
}
pub fn surf(
&mut self,
x: impl Into<GridCoords>,
y: impl Into<GridCoords>,
z: &Matrix,
) -> SurfaceMut<'_> {
let defaults = Surface::default();
self.add_surface(x.into(), y.into(), z, defaults.face, defaults.edge)
}
pub fn mesh(
&mut self,
x: impl Into<GridCoords>,
y: impl Into<GridCoords>,
z: &Matrix,
) -> SurfaceMut<'_> {
let face = ColorSpec::Rgba {
color: self.fig.background,
};
self.add_surface(x.into(), y.into(), z, face, ColorSpec::Colormapped)
}
pub fn title(&mut self, title: impl Into<Text>) -> &mut Self {
self.axes().title = Some(title.into());
self
}
pub fn xlabel(&mut self, label: impl Into<Text>) -> &mut Self {
self.axes().x.label = Some(label.into());
self
}
pub fn ylabel(&mut self, label: impl Into<Text>) -> &mut Self {
self.axes().y.label = Some(label.into());
self
}
pub fn zlabel(&mut self, label: impl Into<Text>) -> &mut Self {
self.axes().z.label = Some(label.into());
self
}
pub fn xlim(&mut self, min: f64, max: f64) -> &mut Self {
self.set_limits(Dimension::X, min, max)
}
pub fn ylim(&mut self, min: f64, max: f64) -> &mut Self {
self.set_limits(Dimension::Y, min, max)
}
pub fn zlim(&mut self, min: f64, max: f64) -> &mut Self {
self.set_limits(Dimension::Z, min, max)
}
pub fn xscale(&mut self, scale: Scale) -> &mut Self {
self.axes().x.scale = scale;
self
}
pub fn yscale(&mut self, scale: Scale) -> &mut Self {
self.axes().y.scale = scale;
self
}
pub fn zscale(&mut self, scale: Scale) -> &mut Self {
self.axes().z.scale = scale;
self
}
pub fn grid(&mut self, on: bool) -> &mut Self {
let axes = self.axes();
for axis in [&mut axes.x, &mut axes.y, &mut axes.z] {
axis.grid = on;
}
self
}
pub fn legend(&mut self, location: LegendLocation) -> &mut Self {
self.axes().legend = Some(Legend {
location,
..Legend::default()
});
self
}
pub fn legend_off(&mut self) -> &mut Self {
self.axes().legend = None;
self
}
pub fn view(&mut self, azimuth_deg: f64, elevation_deg: f64) -> &mut Self {
let axes = self.axes();
let view3d = match axes.projection {
Projection::ThreeD { view3d } => view3d,
Projection::TwoD => View3d::default(),
};
axes.projection = Projection::ThreeD {
view3d: View3d {
azimuth_deg,
elevation_deg,
..view3d
},
};
self
}
pub fn colormap(&mut self, colormap: ColormapName) -> &mut Self {
self.axes().colormap = colormap;
self
}
pub fn clim(&mut self, min: f64, max: f64) -> &mut Self {
self.axes().clim = Limits::Manual { min, max };
self
}
pub fn box_on(&mut self, on: bool) -> &mut Self {
self.axes().box_ = on;
self
}
fn axes(&mut self) -> &mut Axes {
self.fig
.axes_mut(self.id)
.expect("an axes handle always refers to an axes of its figure")
}
pub(crate) fn make_3d(&mut self) {
let axes = self.axes();
if axes.projection == Projection::TwoD {
axes.projection = Projection::ThreeD {
view3d: View3d::default(),
};
}
}
fn set_xy_scales(&mut self, x: Scale, y: Scale) {
let axes = self.axes();
axes.x.scale = x;
axes.y.scale = y;
}
fn set_limits(&mut self, dim: Dimension, min: f64, max: f64) -> &mut Self {
let limits = Limits::Manual { min, max };
match self.fig.set_limits(self.id, dim, limits) {
Ok(()) => {}
Err(IrError::InvalidLimits { .. }) => axis_mut(self.axes(), dim).limits = limits,
Err(error) => unreachable!("an axes handle refers to an axes of its figure: {error}"),
}
self
}
fn store_vector(&mut self, values: &[f64]) -> ironlab_ir::DataId {
self.fig.add_data(NdArray::vector(values.to_vec()))
}
fn push_artist(&mut self, build: impl FnOnce(NodeId) -> Artist) -> NodeId {
let id = self.fig.alloc_node_id();
self.axes().artists.push(build(id));
id
}
fn add_line(&mut self, x: &[f64], y: &[f64], z: Option<&[f64]>) -> LineMut<'_> {
let x = self.store_vector(x);
let y = self.store_vector(y);
let z = z.map(|z| self.store_vector(z));
let id = self.push_artist(|id| {
Artist::Line(Line {
id,
x,
y,
z,
..Line::default()
})
});
LineMut::new(self.fig, id)
}
fn add_scatter(&mut self, x: &[f64], y: &[f64], z: Option<&[f64]>) -> ScatterMut<'_> {
let x = self.store_vector(x);
let y = self.store_vector(y);
let z = z.map(|z| self.store_vector(z));
let id = self.push_artist(|id| {
Artist::Scatter(Scatter {
id,
x,
y,
z,
..Scatter::default()
})
});
ScatterMut::new(self.fig, id)
}
fn add_contour(
&mut self,
x: GridCoords,
y: GridCoords,
z: &Matrix,
fill: bool,
placement: ContourPlacement,
) -> ContourMut<'_> {
let grid = store_grid(self.fig, x, y, z);
let z = self.fig.add_data(matrix_array(z));
let id = self.push_artist(|id| {
Artist::Contour(Contour {
id,
grid,
z,
fill,
placement,
..Contour::default()
})
});
ContourMut::new(self.fig, id)
}
fn add_surface(
&mut self,
x: GridCoords,
y: GridCoords,
z: &Matrix,
face: ColorSpec,
edge: ColorSpec,
) -> SurfaceMut<'_> {
self.make_3d();
let grid = store_grid(self.fig, x, y, z);
let z = self.fig.add_data(matrix_array(z));
let id = self.push_artist(|id| {
Artist::Surface(Surface {
id,
grid,
z,
face,
edge,
..Surface::default()
})
});
SurfaceMut::new(self.fig, id)
}
}
fn axis_mut(axes: &mut Axes, dim: Dimension) -> &mut Axis {
match dim {
Dimension::X => &mut axes.x,
Dimension::Y => &mut axes.y,
Dimension::Z => &mut axes.z,
}
}