Skip to main content

runmat_plot/plots/
reference_line.rs

1//! Reference line plot implementation for MATLAB-compatible xline/yline.
2
3use crate::core::{BoundingBox, RenderData};
4use crate::plots::line::{LinePlot, LineStyle};
5use glam::{Vec3, Vec4};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum ReferenceLineOrientation {
9    Vertical,
10    Horizontal,
11}
12
13#[derive(Debug, Clone)]
14pub struct ReferenceLine {
15    pub orientation: ReferenceLineOrientation,
16    pub value: f64,
17    pub color: Vec4,
18    pub line_width: f32,
19    pub line_style: LineStyle,
20    pub label: Option<String>,
21    pub display_name: Option<String>,
22    pub label_orientation: String,
23    pub visible: bool,
24}
25
26impl ReferenceLine {
27    pub fn new(orientation: ReferenceLineOrientation, value: f64) -> Result<Self, String> {
28        if !value.is_finite() {
29            return Err("reference line coordinate must be finite".to_string());
30        }
31        Ok(Self {
32            orientation,
33            value,
34            color: Vec4::new(0.0, 0.0, 0.0, 1.0),
35            line_width: 1.0,
36            line_style: LineStyle::Solid,
37            label: None,
38            display_name: None,
39            label_orientation: "aligned".to_string(),
40            visible: true,
41        })
42    }
43
44    pub fn with_style(mut self, color: Vec4, line_width: f32, line_style: LineStyle) -> Self {
45        self.color = color;
46        self.line_width = line_width;
47        self.line_style = line_style;
48        self
49    }
50
51    pub fn label_for_legend(&self) -> Option<String> {
52        self.display_name.clone().or_else(|| self.label.clone())
53    }
54
55    pub fn coordinate_bounds(&self) -> BoundingBox {
56        match self.orientation {
57            ReferenceLineOrientation::Vertical => BoundingBox::new(
58                Vec3::new(self.value as f32, 0.0, 0.0),
59                Vec3::new(self.value as f32, 0.0, 0.0),
60            ),
61            ReferenceLineOrientation::Horizontal => BoundingBox::new(
62                Vec3::new(0.0, self.value as f32, 0.0),
63                Vec3::new(0.0, self.value as f32, 0.0),
64            ),
65        }
66    }
67
68    pub fn render_data_with_range(
69        &self,
70        x_range: (f64, f64),
71        y_range: (f64, f64),
72        viewport_px: Option<(u32, u32)>,
73    ) -> RenderData {
74        let (x, y) = match self.orientation {
75            ReferenceLineOrientation::Vertical => {
76                (vec![self.value, self.value], vec![y_range.0, y_range.1])
77            }
78            ReferenceLineOrientation::Horizontal => {
79                (vec![x_range.0, x_range.1], vec![self.value, self.value])
80            }
81        };
82        let mut line = LinePlot::new(x, y)
83            .expect("reference line builds finite two-point line")
84            .with_style(self.color, self.line_width, self.line_style);
85        if let Some(label) = self.label_for_legend() {
86            line = line.with_label(label);
87        }
88        line.render_data_with_viewport(viewport_px)
89    }
90
91    pub fn estimated_memory_usage(&self) -> usize {
92        std::mem::size_of::<Self>()
93            + self.label.as_ref().map_or(0, String::len)
94            + self.display_name.as_ref().map_or(0, String::len)
95            + self.label_orientation.len()
96    }
97}