Skip to main content

runmat_plot/plots/
area.rs

1//! Area plot implementation (filled area under curve)
2
3use crate::core::{
4    BoundingBox, DrawCall, GpuVertexBuffer, Material, PipelineType, RenderData, Vertex,
5};
6use glam::{Vec3, Vec4};
7
8#[derive(Debug, Clone)]
9pub struct AreaPlot {
10    pub x: Vec<f64>,
11    pub y: Vec<f64>,
12    pub baseline: f64,
13    pub lower_y: Option<Vec<f64>>,
14    pub color: Vec4,
15    pub label: Option<String>,
16    pub visible: bool,
17    vertices: Option<Vec<Vertex>>,
18    indices: Option<Vec<u32>>,
19    bounds: Option<BoundingBox>,
20    dirty: bool,
21    gpu_vertices: Option<GpuVertexBuffer>,
22    gpu_vertex_count: Option<usize>,
23    gpu_bounds: Option<BoundingBox>,
24}
25
26impl AreaPlot {
27    pub fn new(x: Vec<f64>, y: Vec<f64>) -> Result<Self, String> {
28        if x.len() != y.len() || x.is_empty() {
29            return Err("area: X and Y must be same non-zero length".to_string());
30        }
31        Ok(Self {
32            x,
33            y,
34            baseline: 0.0,
35            lower_y: None,
36            color: Vec4::new(0.0, 0.5, 1.0, 0.4),
37            label: None,
38            visible: true,
39            vertices: None,
40            indices: None,
41            bounds: None,
42            dirty: true,
43            gpu_vertices: None,
44            gpu_vertex_count: None,
45            gpu_bounds: None,
46        })
47    }
48    pub fn from_gpu_buffer(
49        color: Vec4,
50        baseline: f64,
51        lower_y: Option<Vec<f64>>,
52        buffer: GpuVertexBuffer,
53        vertex_count: usize,
54        bounds: BoundingBox,
55    ) -> Self {
56        Self {
57            x: Vec::new(),
58            y: Vec::new(),
59            baseline,
60            lower_y,
61            color,
62            label: None,
63            visible: true,
64            vertices: None,
65            indices: None,
66            bounds: Some(bounds),
67            dirty: false,
68            gpu_vertices: Some(buffer),
69            gpu_vertex_count: Some(vertex_count),
70            gpu_bounds: Some(bounds),
71        }
72    }
73    pub fn with_style(mut self, color: Vec4, baseline: f64) -> Self {
74        self.color = color;
75        self.baseline = baseline;
76        self.dirty = true;
77        self
78    }
79    pub fn with_lower_curve(mut self, lower_y: Vec<f64>) -> Self {
80        self.lower_y = Some(lower_y);
81        self.dirty = true;
82        self
83    }
84    pub fn with_label<S: Into<String>>(mut self, label: S) -> Self {
85        self.label = Some(label.into());
86        self
87    }
88    pub fn set_visible(&mut self, v: bool) {
89        self.visible = v;
90    }
91    pub fn generate_vertices(&mut self) -> (&Vec<Vertex>, &Vec<u32>) {
92        if self.dirty || self.vertices.is_none() {
93            let mut verts = Vec::new();
94            let mut inds = Vec::new();
95            // Build a triangle strip-like mesh: baseline to curve segments
96            for i in 0..self.x.len() {
97                let xi = self.x[i] as f32;
98                let yi = self.y[i] as f32;
99                let b = self
100                    .lower_y
101                    .as_ref()
102                    .and_then(|vals| vals.get(i).copied())
103                    .unwrap_or(self.baseline) as f32;
104                if !xi.is_finite() || !yi.is_finite() {
105                    continue;
106                }
107                verts.push(Vertex::new(Vec3::new(xi, b, 0.0), self.color));
108                verts.push(Vertex::new(Vec3::new(xi, yi, 0.0), self.color));
109            }
110            // Triangles between successive pairs
111            for i in 0..(verts.len() / 2 - 1) {
112                let base = (i * 2) as u32;
113                inds.extend_from_slice(&[base, base + 1, base + 3, base, base + 3, base + 2]);
114            }
115            self.vertices = Some(verts);
116            self.indices = Some(inds);
117            self.dirty = false;
118        }
119        (
120            self.vertices.as_ref().unwrap(),
121            self.indices.as_ref().unwrap(),
122        )
123    }
124    pub fn bounds(&mut self) -> BoundingBox {
125        if let Some(bounds) = self.gpu_bounds {
126            return bounds;
127        }
128        if self.dirty || self.bounds.is_none() {
129            let mut min = Vec3::new(f32::INFINITY, f32::INFINITY, 0.0);
130            let mut max = Vec3::new(f32::NEG_INFINITY, f32::NEG_INFINITY, 0.0);
131            for (idx, (&x, &y)) in self.x.iter().zip(self.y.iter()).enumerate() {
132                let (x, y) = (x as f32, y as f32);
133                let lower = self
134                    .lower_y
135                    .as_ref()
136                    .and_then(|vals| vals.get(idx).copied())
137                    .unwrap_or(self.baseline) as f32;
138                if !x.is_finite() || !y.is_finite() {
139                    continue;
140                }
141                min.x = min.x.min(x);
142                max.x = max.x.max(x);
143                min.y = min.y.min(y.min(lower));
144                max.y = max.y.max(y.max(lower));
145            }
146            if !min.x.is_finite() {
147                min = Vec3::ZERO;
148                max = Vec3::ZERO;
149            }
150            self.bounds = Some(BoundingBox::new(min, max));
151        }
152        self.bounds.unwrap()
153    }
154    pub fn render_data(&mut self) -> RenderData {
155        let using_gpu = self.gpu_vertices.is_some();
156        let bounds = self.bounds();
157        let (vertices, indices) = if using_gpu {
158            (Vec::new(), Vec::new())
159        } else {
160            let (v, i) = self.generate_vertices();
161            (v.clone(), i.clone())
162        };
163        let material = Material {
164            albedo: self.color,
165            ..Default::default()
166        };
167        let draw_call = DrawCall {
168            vertex_offset: 0,
169            vertex_count: self.gpu_vertex_count.unwrap_or(vertices.len()),
170            index_offset: if using_gpu { None } else { Some(0) },
171            index_count: if using_gpu { None } else { Some(indices.len()) },
172            instance_count: 1,
173        };
174        RenderData {
175            pipeline_type: PipelineType::Triangles,
176            vertices,
177            indices: if using_gpu { None } else { Some(indices) },
178            material,
179            draw_calls: vec![draw_call],
180            gpu_vertices: self.gpu_vertices.clone(),
181            bounds: Some(bounds),
182            image: None,
183        }
184    }
185    pub fn estimated_memory_usage(&self) -> usize {
186        self.vertices
187            .as_ref()
188            .map_or(0, |v| v.len() * std::mem::size_of::<Vertex>())
189            + self
190                .indices
191                .as_ref()
192                .map_or(0, |i| i.len() * std::mem::size_of::<u32>())
193    }
194}