1pub mod bar;
7pub mod bar_groups;
8pub mod digital;
9pub mod dummy;
10pub mod error_bars;
11pub mod heatmap;
12pub mod histogram;
13pub mod image;
14pub mod inf_lines;
15pub mod line;
16pub mod pie;
17pub mod scatter;
18pub mod shaded;
19pub mod stairs;
20pub mod stems;
21pub mod text;
22
23use dear_imgui_rs::{with_scratch_txt, with_scratch_txt_slice, with_scratch_txt_slice_with_opt};
24use std::os::raw::c_char;
25
26pub use bar::*;
28pub use bar_groups::*;
29pub use digital::*;
30pub use dummy::*;
31pub use error_bars::*;
32pub use heatmap::*;
33pub use histogram::*;
34pub use image::*;
35pub use inf_lines::*;
36pub use line::*;
37pub use pie::*;
38pub use scatter::*;
39pub use shaded::*;
40pub use stairs::*;
41pub use stems::*;
42pub use text::*;
43
44pub trait Plot {
46 fn plot(&self);
48
49 fn label(&self) -> &str;
51}
52
53pub trait PlotData {
55 fn label(&self) -> &str;
57
58 fn data_len(&self) -> usize;
60
61 fn is_empty(&self) -> bool {
63 self.data_len() == 0
64 }
65
66 fn validate(&self) -> Result<(), PlotError> {
68 if self.is_empty() {
69 Err(PlotError::EmptyData)
70 } else {
71 Ok(())
72 }
73 }
74}
75
76#[derive(Debug, Clone, PartialEq)]
78pub enum PlotError {
79 DataLengthMismatch { x_len: usize, y_len: usize },
81 EmptyData,
83 InvalidData(String),
85 StringConversion(String),
87 PlotCreationFailed(String),
89}
90
91impl std::fmt::Display for PlotError {
92 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
93 match self {
94 PlotError::DataLengthMismatch { x_len, y_len } => {
95 write!(
96 f,
97 "Data length mismatch: x has {} elements, y has {} elements",
98 x_len, y_len
99 )
100 }
101 PlotError::EmptyData => write!(f, "Data is empty"),
102 PlotError::InvalidData(msg) => write!(f, "Invalid data: {}", msg),
103 PlotError::StringConversion(msg) => write!(f, "String conversion error: {}", msg),
104 PlotError::PlotCreationFailed(msg) => write!(f, "Plot creation failed: {}", msg),
105 }
106 }
107}
108
109impl std::error::Error for PlotError {}
110
111pub fn validate_data_lengths<T, U>(data1: &[T], data2: &[U]) -> Result<(), PlotError> {
116 if data1.is_empty() || data2.is_empty() {
117 return Err(PlotError::EmptyData);
118 }
119
120 if data1.len() != data2.len() {
121 return Err(PlotError::DataLengthMismatch {
122 x_len: data1.len(),
123 y_len: data2.len(),
124 });
125 }
126
127 Ok(())
128}
129
130pub(crate) fn with_plot_str<R>(s: &str, f: impl FnOnce(*const c_char) -> R) -> Option<R> {
131 if s.contains('\0') {
132 None
133 } else {
134 Some(with_scratch_txt(s, f))
135 }
136}
137
138pub(crate) fn with_plot_str_or_empty<R>(s: &str, f: impl FnOnce(*const c_char) -> R) -> R {
139 let s = if s.contains('\0') { "" } else { s };
140 with_scratch_txt(s, f)
141}
142
143pub(crate) fn with_plot_str_slice<R>(txts: &[&str], f: impl FnOnce(&[*const c_char]) -> R) -> R {
144 let cleaned: Vec<&str> = txts
145 .iter()
146 .map(|&s| if s.contains('\0') { "" } else { s })
147 .collect();
148 with_scratch_txt_slice(&cleaned, f)
149}
150
151pub(crate) fn with_plot_str_slice_with_opt<R>(
152 txts: &[&str],
153 txt_opt: Option<&str>,
154 f: impl FnOnce(&[*const c_char], *const c_char) -> R,
155) -> R {
156 let cleaned: Vec<&str> = txts
157 .iter()
158 .map(|&s| if s.contains('\0') { "" } else { s })
159 .collect();
160 let txt_opt = txt_opt.filter(|s| !s.contains('\0'));
161 with_scratch_txt_slice_with_opt(&cleaned, txt_opt, f)
162}
163
164pub struct PlotBuilder<'a> {
166 plot_type: PlotType<'a>,
167}
168
169pub enum PlotType<'a> {
171 Line {
172 label: &'a str,
173 x_data: &'a [f64],
174 y_data: &'a [f64],
175 },
176 Scatter {
177 label: &'a str,
178 x_data: &'a [f64],
179 y_data: &'a [f64],
180 },
181 Bar {
182 label: &'a str,
183 values: &'a [f64],
184 width: f64,
185 },
186 Histogram {
187 label: &'a str,
188 values: &'a [f64],
189 bins: i32,
190 },
191 Heatmap {
192 label: &'a str,
193 values: &'a [f64],
194 rows: usize,
195 cols: usize,
196 },
197 PieChart {
198 labels: Vec<&'a str>,
199 values: &'a [f64],
200 center: (f64, f64),
201 radius: f64,
202 },
203}
204
205impl<'a> PlotBuilder<'a> {
206 pub fn line(label: &'a str, x_data: &'a [f64], y_data: &'a [f64]) -> Self {
208 Self {
209 plot_type: PlotType::Line {
210 label,
211 x_data,
212 y_data,
213 },
214 }
215 }
216
217 pub fn scatter(label: &'a str, x_data: &'a [f64], y_data: &'a [f64]) -> Self {
219 Self {
220 plot_type: PlotType::Scatter {
221 label,
222 x_data,
223 y_data,
224 },
225 }
226 }
227
228 pub fn bar(label: &'a str, values: &'a [f64]) -> Self {
230 Self {
231 plot_type: PlotType::Bar {
232 label,
233 values,
234 width: 0.67,
235 },
236 }
237 }
238
239 pub fn histogram(label: &'a str, values: &'a [f64]) -> Self {
241 Self {
242 plot_type: PlotType::Histogram {
243 label,
244 values,
245 bins: crate::BinMethod::Sturges as i32,
246 },
247 }
248 }
249
250 pub fn heatmap(label: &'a str, values: &'a [f64], rows: usize, cols: usize) -> Self {
252 Self {
253 plot_type: PlotType::Heatmap {
254 label,
255 values,
256 rows,
257 cols,
258 },
259 }
260 }
261
262 pub fn pie_chart(
264 labels: Vec<&'a str>,
265 values: &'a [f64],
266 center: (f64, f64),
267 radius: f64,
268 ) -> Self {
269 Self {
270 plot_type: PlotType::PieChart {
271 labels,
272 values,
273 center,
274 radius,
275 },
276 }
277 }
278
279 pub fn build(self) -> Result<(), PlotError> {
281 match self.plot_type {
282 PlotType::Line {
283 label,
284 x_data,
285 y_data,
286 } => {
287 let plot = line::LinePlot::new(label, x_data, y_data);
288 plot.validate()?;
289 plot.plot();
290 }
291 PlotType::Scatter {
292 label,
293 x_data,
294 y_data,
295 } => {
296 let plot = scatter::ScatterPlot::new(label, x_data, y_data);
297 plot.validate()?;
298 plot.plot();
299 }
300 PlotType::Bar {
301 label,
302 values,
303 width,
304 } => {
305 let plot = bar::BarPlot::new(label, values).with_bar_size(width);
306 plot.validate()?;
307 plot.plot();
308 }
309 PlotType::Histogram {
310 label,
311 values,
312 bins,
313 } => {
314 let plot = histogram::HistogramPlot::new(label, values).with_bins(bins);
315 plot.validate()?;
316 plot.plot();
317 }
318 PlotType::Heatmap {
319 label,
320 values,
321 rows,
322 cols,
323 } => {
324 let plot = heatmap::HeatmapPlot::new(label, values, rows, cols);
325 plot.validate()?;
326 plot.plot();
327 }
328 PlotType::PieChart {
329 labels,
330 values,
331 center,
332 radius,
333 } => {
334 let plot = pie::PieChartPlot::new(labels, values, center.0, center.1, radius);
335 plot.validate()?;
336 plot.plot();
337 }
338 }
339 Ok(())
340 }
341}