use ironlab_ir::{
Artist, Contour, DashStyle, DataId, Figure, Image, ImagePlane, IndexedImage, Levels, Line,
MappedImage, MarkerShape, NdArray, NodeId, OutOfRange, PixelRange, Quiver, QuiverScale,
Scatter, ScatterColor, ScatterSize, Surface, Text,
};
use crate::axes::AxesMut;
use crate::color::IntoColorSpec;
use crate::grid::matrix_array;
use crate::matrix::Matrix;
macro_rules! artist_handle {
($handle:ident, $variant:ident) => {
impl<'a> $handle<'a> {
pub(crate) fn new(fig: &'a mut Figure, id: NodeId) -> Self {
Self { fig, id }
}
fn artist(&mut self) -> &mut $variant {
match self.fig.artist_mut(self.id) {
Some(Artist::$variant(artist)) => artist,
_ => unreachable!(concat!(
"a ",
stringify!($handle),
" handle always refers to an artist of its kind"
)),
}
}
}
};
}
artist_handle!(LineMut, Line);
artist_handle!(ScatterMut, Scatter);
artist_handle!(ContourMut, Contour);
artist_handle!(QuiverMut, Quiver);
artist_handle!(SurfaceMut, Surface);
macro_rules! image_handle {
($handle:ident, $variant:ident) => {
artist_handle!($handle, $variant);
impl $handle<'_> {
#[must_use]
pub fn id(&self) -> NodeId {
self.id
}
pub fn display_name(&mut self, name: impl Into<Text>) -> &mut Self {
self.artist().display_name = Some(name.into());
self
}
pub fn pixel_columns(&mut self, first: f64, last: f64) -> &mut Self {
self.artist().placement.columns = Some(PixelRange { first, last });
self
}
pub fn pixel_rows(&mut self, first: f64, last: f64) -> &mut Self {
self.artist().placement.rows = Some(PixelRange { first, last });
self
}
pub fn plane(&mut self, plane: ImagePlane) -> &mut Self {
self.artist().placement.plane = plane;
make_3d_for_wall(self.fig, self.id, plane);
self
}
}
};
}
#[derive(Debug)]
pub struct LineMut<'a> {
pub(crate) fig: &'a mut Figure,
pub(crate) id: NodeId,
}
impl LineMut<'_> {
#[must_use]
pub fn id(&self) -> NodeId {
self.id
}
pub fn display_name(&mut self, name: impl Into<Text>) -> &mut Self {
self.artist().display_name = Some(name.into());
self
}
pub fn color(&mut self, color: impl IntoColorSpec) -> &mut Self {
self.artist().line.color = color.into_color_spec();
self
}
pub fn line_width(&mut self, width_pt: f64) -> &mut Self {
self.artist().line.width_pt = width_pt;
self
}
pub fn dash(&mut self, dash: DashStyle) -> &mut Self {
self.artist().line.dash = dash;
self
}
pub fn marker(&mut self, shape: MarkerShape) -> &mut Self {
self.artist().marker.shape = shape;
self
}
pub fn marker_size(&mut self, size_pt: f64) -> &mut Self {
self.artist().marker.size_pt = size_pt;
self
}
pub fn marker_face(&mut self, color: impl IntoColorSpec) -> &mut Self {
self.artist().marker.face = color.into_color_spec();
self
}
pub fn marker_edge(&mut self, color: impl IntoColorSpec) -> &mut Self {
self.artist().marker.edge = color.into_color_spec();
self
}
}
#[derive(Debug)]
pub struct ScatterMut<'a> {
pub(crate) fig: &'a mut Figure,
pub(crate) id: NodeId,
}
impl ScatterMut<'_> {
#[must_use]
pub fn id(&self) -> NodeId {
self.id
}
pub fn display_name(&mut self, name: impl Into<Text>) -> &mut Self {
self.artist().display_name = Some(name.into());
self
}
pub fn size(&mut self, size_pt: f64) -> &mut Self {
let previous = std::mem::replace(
&mut self.artist().size,
ScatterSize::Scalar { value: size_pt },
);
if let ScatterSize::Data { data } = previous {
release_data(self.fig, data);
}
self
}
pub fn sizes(&mut self, sizes_pt: impl AsRef<[f64]>) -> &mut Self {
let data = self
.fig
.add_data(NdArray::vector(sizes_pt.as_ref().to_vec()));
let previous = std::mem::replace(&mut self.artist().size, ScatterSize::Data { data });
if let ScatterSize::Data { data } = previous {
release_data(self.fig, data);
}
self
}
pub fn color(&mut self, color: impl IntoColorSpec) -> &mut Self {
let spec = color.into_color_spec();
let previous = std::mem::replace(&mut self.artist().color, ScatterColor::Spec { spec });
if let ScatterColor::Data { data } = previous {
release_data(self.fig, data);
}
self
}
pub fn colors(&mut self, values: impl AsRef<[f64]>) -> &mut Self {
let data = self.fig.add_data(NdArray::vector(values.as_ref().to_vec()));
let previous = std::mem::replace(&mut self.artist().color, ScatterColor::Data { data });
if let ScatterColor::Data { data } = previous {
release_data(self.fig, data);
}
self
}
pub fn marker(&mut self, shape: MarkerShape) -> &mut Self {
self.artist().marker.shape = shape;
self
}
pub fn filled(&mut self) -> &mut Self {
self.artist().marker.face = ironlab_ir::ColorSpec::Auto;
self
}
}
#[derive(Debug)]
pub struct ContourMut<'a> {
pub(crate) fig: &'a mut Figure,
pub(crate) id: NodeId,
}
impl ContourMut<'_> {
#[must_use]
pub fn id(&self) -> NodeId {
self.id
}
pub fn display_name(&mut self, name: impl Into<Text>) -> &mut Self {
self.artist().display_name = Some(name.into());
self
}
pub fn levels(&mut self, count: u32) -> &mut Self {
self.artist().levels = Levels::Auto { count };
self
}
pub fn level_values(&mut self, levels: impl AsRef<[f64]>) -> &mut Self {
self.artist().levels = Levels::Explicit {
values: levels.as_ref().to_vec(),
};
self
}
pub fn line_width(&mut self, width_pt: f64) -> &mut Self {
self.artist().line.width_pt = width_pt;
self
}
pub fn color(&mut self, color: impl IntoColorSpec) -> &mut Self {
self.artist().line.color = color.into_color_spec();
self
}
}
#[derive(Debug)]
pub struct QuiverMut<'a> {
pub(crate) fig: &'a mut Figure,
pub(crate) id: NodeId,
}
impl QuiverMut<'_> {
#[must_use]
pub fn id(&self) -> NodeId {
self.id
}
pub fn display_name(&mut self, name: impl Into<Text>) -> &mut Self {
self.artist().display_name = Some(name.into());
self
}
pub fn scale(&mut self, factor: f64) -> &mut Self {
self.artist().scale = QuiverScale::Factor { value: factor };
self
}
pub fn no_scale(&mut self) -> &mut Self {
self.artist().scale = QuiverScale::Off;
self
}
pub fn color(&mut self, color: impl IntoColorSpec) -> &mut Self {
self.artist().line.color = color.into_color_spec();
self
}
pub fn line_width(&mut self, width_pt: f64) -> &mut Self {
self.artist().line.width_pt = width_pt;
self
}
pub fn head_size(&mut self, fraction: f64) -> &mut Self {
self.artist().head_size = fraction;
self
}
}
#[derive(Debug)]
pub struct SurfaceMut<'a> {
pub(crate) fig: &'a mut Figure,
pub(crate) id: NodeId,
}
impl SurfaceMut<'_> {
#[must_use]
pub fn id(&self) -> NodeId {
self.id
}
pub fn display_name(&mut self, name: impl Into<Text>) -> &mut Self {
self.artist().display_name = Some(name.into());
self
}
pub fn face_color(&mut self, color: impl IntoColorSpec) -> &mut Self {
self.artist().face = color.into_color_spec();
self
}
pub fn edge_color(&mut self, color: impl IntoColorSpec) -> &mut Self {
self.artist().edge = color.into_color_spec();
self
}
pub fn edge_width(&mut self, width_pt: f64) -> &mut Self {
self.artist().edge_width_pt = width_pt;
self
}
pub fn color_data(&mut self, c: &Matrix) -> &mut Self {
let data = self.fig.add_data(matrix_array(c));
if let Some(previous) = self.artist().c.replace(data) {
release_data(self.fig, previous);
}
self
}
}
#[derive(Debug)]
pub struct ImageMut<'a> {
pub(crate) fig: &'a mut Figure,
pub(crate) id: NodeId,
}
image_handle!(ImageMut, Image);
#[derive(Debug)]
pub struct IndexedImageMut<'a> {
pub(crate) fig: &'a mut Figure,
pub(crate) id: NodeId,
}
image_handle!(IndexedImageMut, IndexedImage);
impl IndexedImageMut<'_> {
pub fn below(&mut self, policy: impl Into<OutOfRange>) -> &mut Self {
self.artist().below = policy.into();
self
}
pub fn above(&mut self, policy: impl Into<OutOfRange>) -> &mut Self {
self.artist().above = policy.into();
self
}
pub fn non_finite(&mut self, policy: impl Into<OutOfRange>) -> &mut Self {
self.artist().non_finite = policy.into();
self
}
}
#[derive(Debug)]
pub struct MappedImageMut<'a> {
pub(crate) fig: &'a mut Figure,
pub(crate) id: NodeId,
}
image_handle!(MappedImageMut, MappedImage);
impl MappedImageMut<'_> {
pub fn below(&mut self, policy: impl Into<OutOfRange>) -> &mut Self {
self.artist().below = policy.into();
self
}
pub fn above(&mut self, policy: impl Into<OutOfRange>) -> &mut Self {
self.artist().above = policy.into();
self
}
pub fn non_finite(&mut self, policy: impl Into<OutOfRange>) -> &mut Self {
self.artist().non_finite = policy.into();
self
}
}
fn make_3d_for_wall(fig: &mut Figure, id: NodeId, plane: ImagePlane) {
if matches!(plane, ImagePlane::Xy { .. }) {
return;
}
let axes_id = fig
.artist(id)
.map(|(axes, _)| axes.id)
.expect("an image handle always refers to an artist of its figure");
AxesMut::new(fig, axes_id).make_3d();
}
fn release_data(fig: &mut Figure, id: DataId) {
let referenced = fig
.axes
.iter()
.flat_map(|axes| &axes.artists)
.any(|artist| data_ids(artist).contains(&id));
if !referenced {
fig.data.remove(&id);
}
}
fn data_ids(artist: &Artist) -> Vec<DataId> {
let grid_ids = |grid: &ironlab_ir::Grid| match *grid {
ironlab_ir::Grid::Rectilinear { x, y } | ironlab_ir::Grid::Curvilinear { x, y } => [x, y],
};
match artist {
Artist::Line(Line { x, y, z, .. }) => [*x, *y].into_iter().chain(*z).collect(),
Artist::Scatter(Scatter {
x,
y,
z,
size,
color,
..
}) => {
let size = match size {
ScatterSize::Data { data } => Some(*data),
ScatterSize::Scalar { .. } => None,
};
let color = match color {
ScatterColor::Data { data } => Some(*data),
ScatterColor::Spec { .. } => None,
};
[*x, *y]
.into_iter()
.chain(*z)
.chain(size)
.chain(color)
.collect()
}
Artist::Contour(Contour { grid, z, .. }) => {
grid_ids(grid).into_iter().chain([*z]).collect()
}
Artist::Quiver(Quiver {
x, y, z, u, v, w, ..
}) => [*x, *y, *u, *v].into_iter().chain(*z).chain(*w).collect(),
Artist::Surface(Surface { grid, z, c, .. }) => {
grid_ids(grid).into_iter().chain([*z]).chain(*c).collect()
}
Artist::Image(Image { pixels, .. }) => vec![*pixels],
Artist::IndexedImage(IndexedImage { indices, .. }) => vec![*indices],
Artist::MappedImage(MappedImage { values, .. }) => vec![*values],
}
}