poincare-lib 0.5.0

GPU-accelerated 3D plotting library for mathematical functions and scientific visualisation
Documentation
use crate::graph_spec::{ArrowAnnotation, PointAnnotation};
use crate::{
    CoordinateSystem, DataBounds, Domain, GlyphInstance, PlotComponent, PlotGeometry, PlotObject,
    PlotStyle, Resolution, Scatter3D,
};
use viewport_lib::LabelItem;

pub struct AnnotatedPointsPlot {
    pub points: Vec<PointAnnotation>,
    pub show_labels: bool,
    pub style: PlotStyle,
}

pub struct AnnotatedArrowsPlot {
    pub arrows: Vec<ArrowAnnotation>,
    pub show_labels: bool,
    pub style: PlotStyle,
}

impl PlotObject for AnnotatedPointsPlot {
    fn coordinate_system(&self) -> CoordinateSystem {
        CoordinateSystem::Cartesian
    }

    fn natural_bounds(&self) -> Option<DataBounds> {
        bounds_for_points(self.points.iter().map(|point| glam::Vec3::from_array(point.position)))
    }

    fn generate(&self, _domain: &Domain, _resolution: Resolution) -> PlotGeometry {
        let positions: Vec<glam::Vec3> = self
            .points
            .iter()
            .map(|point| glam::Vec3::from_array(point.position))
            .collect();
        let mut components = Vec::new();
        if !positions.is_empty() {
            components.push(PlotComponent {
                geometry: Scatter3D::from_points(&positions)
                    .generate(&Domain::default(), Resolution::default()),
                style: self.style.clone(),
            });
        }
        if self.show_labels {
            let labels = self
                .points
                .iter()
                .filter(|point| !point.label.trim().is_empty())
                .map(|point| {
                    let mut label = LabelItem::default();
                    label.text = point.label.clone();
                    label.world_anchor = Some(point.position);
                    label
                })
                .collect::<Vec<_>>();
            if !labels.is_empty() {
                components.push(PlotComponent {
                    geometry: PlotGeometry::Labels(labels),
                    style: self.style.clone(),
                });
            }
        }
        PlotGeometry::Composite(components)
    }

    fn style(&self) -> &PlotStyle {
        &self.style
    }
}

impl PlotObject for AnnotatedArrowsPlot {
    fn coordinate_system(&self) -> CoordinateSystem {
        CoordinateSystem::Cartesian
    }

    fn natural_bounds(&self) -> Option<DataBounds> {
        bounds_for_points(self.arrows.iter().flat_map(|arrow| {
            let origin = glam::Vec3::from_array(arrow.origin);
            let tip = origin + glam::Vec3::from_array(arrow.vector);
            [origin, tip]
        }))
    }

    fn generate(&self, _domain: &Domain, _resolution: Resolution) -> PlotGeometry {
        let glyphs = self
            .arrows
            .iter()
            .map(|arrow| {
                let vector = glam::Vec3::from_array(arrow.vector);
                GlyphInstance {
                    position: glam::Vec3::from_array(arrow.origin),
                    vector,
                    raw_vector: vector,
                }
            })
            .collect::<Vec<_>>();
        let mut components = vec![PlotComponent {
            geometry: PlotGeometry::Glyphs(glyphs),
            style: self.style.clone(),
        }];
        if self.show_labels {
            let labels = self
                .arrows
                .iter()
                .filter(|arrow| !arrow.label.trim().is_empty())
                .map(|arrow| {
                    let mut label = LabelItem::default();
                    let origin = glam::Vec3::from_array(arrow.origin);
                    let tip = origin + glam::Vec3::from_array(arrow.vector);
                    label.text = arrow.label.clone();
                    label.world_anchor = Some(tip.to_array());
                    label
                })
                .collect::<Vec<_>>();
            if !labels.is_empty() {
                components.push(PlotComponent {
                    geometry: PlotGeometry::Labels(labels),
                    style: self.style.clone(),
                });
            }
        }
        PlotGeometry::Composite(components)
    }

    fn style(&self) -> &PlotStyle {
        &self.style
    }
}

fn bounds_for_points(points: impl IntoIterator<Item = glam::Vec3>) -> Option<DataBounds> {
    let mut min = glam::Vec3::splat(f32::INFINITY);
    let mut max = glam::Vec3::splat(f32::NEG_INFINITY);
    let mut any = false;
    for point in points {
        min = min.min(point);
        max = max.max(point);
        any = true;
    }
    any.then_some(DataBounds {
        x: min.x as f64..=max.x as f64,
        y: min.y as f64..=max.y as f64,
        z: min.z as f64..=max.z as f64,
    })
}