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 crate::sys;
24use dear_imgui_rs::{with_scratch_txt, with_scratch_txt_slice, with_scratch_txt_slice_with_opt};
25use std::os::raw::c_char;
26
27pub use bar::*;
29pub use bar_groups::*;
30pub use digital::*;
31pub use dummy::*;
32pub use error_bars::*;
33pub use heatmap::*;
34pub use histogram::*;
35pub use image::*;
36pub use inf_lines::*;
37pub use line::*;
38pub use pie::*;
39pub use scatter::*;
40pub use shaded::*;
41pub use stairs::*;
42pub use stems::*;
43pub use text::*;
44
45pub trait Plot {
47 fn plot(&self);
49
50 fn label(&self) -> &str;
52}
53
54pub trait PlotData {
56 fn label(&self) -> &str;
58
59 fn data_len(&self) -> usize;
61
62 fn is_empty(&self) -> bool {
64 self.data_len() == 0
65 }
66
67 fn validate(&self) -> Result<(), PlotError> {
69 if self.is_empty() {
70 Err(PlotError::EmptyData)
71 } else {
72 Ok(())
73 }
74 }
75}
76
77#[derive(Debug, Clone, PartialEq)]
79pub enum PlotError {
80 DataLengthMismatch { x_len: usize, y_len: usize },
82 EmptyData,
84 InvalidData(String),
86 StringConversion(String),
88 PlotCreationFailed(String),
90}
91
92impl std::fmt::Display for PlotError {
93 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
94 match self {
95 PlotError::DataLengthMismatch { x_len, y_len } => {
96 write!(
97 f,
98 "Data length mismatch: x has {} elements, y has {} elements",
99 x_len, y_len
100 )
101 }
102 PlotError::EmptyData => write!(f, "Data is empty"),
103 PlotError::InvalidData(msg) => write!(f, "Invalid data: {}", msg),
104 PlotError::StringConversion(msg) => write!(f, "String conversion error: {}", msg),
105 PlotError::PlotCreationFailed(msg) => write!(f, "Plot creation failed: {}", msg),
106 }
107 }
108}
109
110impl std::error::Error for PlotError {}
111
112pub fn validate_data_lengths<T, U>(data1: &[T], data2: &[U]) -> Result<(), PlotError> {
117 if data1.is_empty() || data2.is_empty() {
118 return Err(PlotError::EmptyData);
119 }
120
121 if data1.len() != data2.len() {
122 return Err(PlotError::DataLengthMismatch {
123 x_len: data1.len(),
124 y_len: data2.len(),
125 });
126 }
127
128 Ok(())
129}
130
131pub(crate) fn with_plot_str<R>(s: &str, f: impl FnOnce(*const c_char) -> R) -> Option<R> {
132 if s.contains('\0') {
133 None
134 } else {
135 Some(with_scratch_txt(s, f))
136 }
137}
138
139pub(crate) fn with_plot_str_or_empty<R>(s: &str, f: impl FnOnce(*const c_char) -> R) -> R {
140 let s = if s.contains('\0') { "" } else { s };
141 with_scratch_txt(s, f)
142}
143
144pub(crate) fn with_plot_str_slice<R>(txts: &[&str], f: impl FnOnce(&[*const c_char]) -> R) -> R {
145 let cleaned: Vec<&str> = txts
146 .iter()
147 .map(|&s| if s.contains('\0') { "" } else { s })
148 .collect();
149 with_scratch_txt_slice(&cleaned, f)
150}
151
152pub(crate) fn with_plot_str_slice_with_opt<R>(
153 txts: &[&str],
154 txt_opt: Option<&str>,
155 f: impl FnOnce(&[*const c_char], *const c_char) -> R,
156) -> R {
157 let cleaned: Vec<&str> = txts
158 .iter()
159 .map(|&s| if s.contains('\0') { "" } else { s })
160 .collect();
161 let txt_opt = txt_opt.filter(|s| !s.contains('\0'));
162 with_scratch_txt_slice_with_opt(&cleaned, txt_opt, f)
163}
164
165pub(crate) fn default_plot_spec() -> sys::ImPlotSpec_c {
166 let auto_col = sys::ImVec4_c {
167 x: 0.0,
168 y: 0.0,
169 z: 0.0,
170 w: -1.0,
171 };
172
173 sys::ImPlotSpec_c {
174 LineColor: auto_col,
175 LineWeight: 1.0,
176 FillColor: auto_col,
177 FillAlpha: 1.0,
178 Marker: sys::ImPlotMarker_None as _,
179 MarkerSize: 4.0,
180 MarkerLineColor: auto_col,
181 MarkerFillColor: auto_col,
182 Size: 4.0,
183 Offset: 0,
184 Stride: crate::IMPLOT_AUTO,
185 Flags: sys::ImPlotItemFlags_None as _,
186 }
187}
188
189pub(crate) fn plot_spec_from(flags: u32, offset: i32, stride: i32) -> sys::ImPlotSpec_c {
190 let mut spec = default_plot_spec();
191 spec.Flags = flags as sys::ImPlotItemFlags;
192 spec.Offset = offset;
193 spec.Stride = stride;
194 spec
195}
196
197pub struct PlotBuilder<'a> {
199 plot_type: PlotType<'a>,
200}
201
202pub enum PlotType<'a> {
204 Line {
205 label: &'a str,
206 x_data: &'a [f64],
207 y_data: &'a [f64],
208 },
209 Scatter {
210 label: &'a str,
211 x_data: &'a [f64],
212 y_data: &'a [f64],
213 },
214 Bar {
215 label: &'a str,
216 values: &'a [f64],
217 width: f64,
218 },
219 Histogram {
220 label: &'a str,
221 values: &'a [f64],
222 bins: i32,
223 },
224 Heatmap {
225 label: &'a str,
226 values: &'a [f64],
227 rows: usize,
228 cols: usize,
229 },
230 PieChart {
231 labels: Vec<&'a str>,
232 values: &'a [f64],
233 center: (f64, f64),
234 radius: f64,
235 },
236}
237
238impl<'a> PlotBuilder<'a> {
239 pub fn line(label: &'a str, x_data: &'a [f64], y_data: &'a [f64]) -> Self {
241 Self {
242 plot_type: PlotType::Line {
243 label,
244 x_data,
245 y_data,
246 },
247 }
248 }
249
250 pub fn scatter(label: &'a str, x_data: &'a [f64], y_data: &'a [f64]) -> Self {
252 Self {
253 plot_type: PlotType::Scatter {
254 label,
255 x_data,
256 y_data,
257 },
258 }
259 }
260
261 pub fn bar(label: &'a str, values: &'a [f64]) -> Self {
263 Self {
264 plot_type: PlotType::Bar {
265 label,
266 values,
267 width: 0.67,
268 },
269 }
270 }
271
272 pub fn histogram(label: &'a str, values: &'a [f64]) -> Self {
274 Self {
275 plot_type: PlotType::Histogram {
276 label,
277 values,
278 bins: crate::BinMethod::Sturges as i32,
279 },
280 }
281 }
282
283 pub fn heatmap(label: &'a str, values: &'a [f64], rows: usize, cols: usize) -> Self {
285 Self {
286 plot_type: PlotType::Heatmap {
287 label,
288 values,
289 rows,
290 cols,
291 },
292 }
293 }
294
295 pub fn pie_chart(
297 labels: Vec<&'a str>,
298 values: &'a [f64],
299 center: (f64, f64),
300 radius: f64,
301 ) -> Self {
302 Self {
303 plot_type: PlotType::PieChart {
304 labels,
305 values,
306 center,
307 radius,
308 },
309 }
310 }
311
312 pub fn build(self) -> Result<(), PlotError> {
314 match self.plot_type {
315 PlotType::Line {
316 label,
317 x_data,
318 y_data,
319 } => {
320 let plot = line::LinePlot::new(label, x_data, y_data);
321 plot.validate()?;
322 plot.plot();
323 }
324 PlotType::Scatter {
325 label,
326 x_data,
327 y_data,
328 } => {
329 let plot = scatter::ScatterPlot::new(label, x_data, y_data);
330 plot.validate()?;
331 plot.plot();
332 }
333 PlotType::Bar {
334 label,
335 values,
336 width,
337 } => {
338 let plot = bar::BarPlot::new(label, values).with_bar_size(width);
339 plot.validate()?;
340 plot.plot();
341 }
342 PlotType::Histogram {
343 label,
344 values,
345 bins,
346 } => {
347 let plot = histogram::HistogramPlot::new(label, values).with_bins(bins);
348 plot.validate()?;
349 plot.plot();
350 }
351 PlotType::Heatmap {
352 label,
353 values,
354 rows,
355 cols,
356 } => {
357 let plot = heatmap::HeatmapPlot::new(label, values, rows, cols);
358 plot.validate()?;
359 plot.plot();
360 }
361 PlotType::PieChart {
362 labels,
363 values,
364 center,
365 radius,
366 } => {
367 let plot = pie::PieChartPlot::new(labels, values, center.0, center.1, radius);
368 plot.validate()?;
369 plot.plot();
370 }
371 }
372 Ok(())
373 }
374}