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 polygon;
18pub mod scatter;
19pub mod shaded;
20pub mod stairs;
21pub mod stems;
22pub mod text;
23
24use crate::sys;
25use dear_imgui_rs::{with_scratch_txt, with_scratch_txt_slice, with_scratch_txt_slice_with_opt};
26use std::cell::RefCell;
27use std::os::raw::c_char;
28
29pub use bar::*;
31pub use bar_groups::*;
32pub use digital::*;
33pub use dummy::*;
34pub use error_bars::*;
35pub use heatmap::*;
36pub use histogram::*;
37pub use image::*;
38pub use inf_lines::*;
39pub use line::*;
40pub use pie::*;
41pub use polygon::*;
42pub use scatter::*;
43pub use shaded::*;
44pub use stairs::*;
45pub use stems::*;
46pub use text::*;
47
48thread_local! {
49 static NEXT_PLOT_SPEC: RefCell<Option<sys::ImPlotSpec_c>> = RefCell::new(None);
50}
51
52fn color4(rgba: [f32; 4]) -> sys::ImVec4_c {
53 sys::ImVec4_c {
54 x: rgba[0],
55 y: rgba[1],
56 z: rgba[2],
57 w: rgba[3],
58 }
59}
60
61#[derive(Debug, Clone, Copy, Default, PartialEq)]
66pub struct PlotItemStyle {
67 pub(crate) line_color: Option<sys::ImVec4_c>,
68 pub(crate) line_weight: Option<f32>,
69 pub(crate) fill_color: Option<sys::ImVec4_c>,
70 pub(crate) fill_alpha: Option<f32>,
71 pub(crate) marker: Option<sys::ImPlotMarker>,
72 pub(crate) marker_size: Option<f32>,
73 pub(crate) marker_line_color: Option<sys::ImVec4_c>,
74 pub(crate) marker_fill_color: Option<sys::ImVec4_c>,
75 pub(crate) size: Option<f32>,
76}
77
78impl PlotItemStyle {
79 pub fn new() -> Self {
81 Self::default()
82 }
83
84 pub fn with_line_color(mut self, color: [f32; 4]) -> Self {
86 self.line_color = Some(color4(color));
87 self
88 }
89
90 pub fn with_line_weight(mut self, weight: f32) -> Self {
92 self.line_weight = Some(weight);
93 self
94 }
95
96 pub fn with_fill_color(mut self, color: [f32; 4]) -> Self {
98 self.fill_color = Some(color4(color));
99 self
100 }
101
102 pub fn with_fill_alpha(mut self, alpha: f32) -> Self {
104 self.fill_alpha = Some(alpha);
105 self
106 }
107
108 pub fn with_marker(mut self, marker: crate::Marker) -> Self {
110 self.marker = Some(marker as sys::ImPlotMarker);
111 self
112 }
113
114 pub fn with_marker_size(mut self, size: f32) -> Self {
116 self.marker_size = Some(size);
117 self
118 }
119
120 pub fn with_marker_line_color(mut self, color: [f32; 4]) -> Self {
122 self.marker_line_color = Some(color4(color));
123 self
124 }
125
126 pub fn with_marker_fill_color(mut self, color: [f32; 4]) -> Self {
128 self.marker_fill_color = Some(color4(color));
129 self
130 }
131
132 pub fn with_size(mut self, size: f32) -> Self {
134 self.size = Some(size);
135 self
136 }
137
138 pub(crate) fn apply_to_spec(self, spec: &mut sys::ImPlotSpec_c) {
139 if let Some(line_color) = self.line_color {
140 spec.LineColor = line_color;
141 }
142 if let Some(line_weight) = self.line_weight {
143 spec.LineWeight = line_weight;
144 }
145 if let Some(fill_color) = self.fill_color {
146 spec.FillColor = fill_color;
147 }
148 if let Some(fill_alpha) = self.fill_alpha {
149 spec.FillAlpha = fill_alpha;
150 }
151 if let Some(marker) = self.marker {
152 spec.Marker = marker;
153 }
154 if let Some(marker_size) = self.marker_size {
155 spec.MarkerSize = marker_size;
156 }
157 if let Some(marker_line_color) = self.marker_line_color {
158 spec.MarkerLineColor = marker_line_color;
159 }
160 if let Some(marker_fill_color) = self.marker_fill_color {
161 spec.MarkerFillColor = marker_fill_color;
162 }
163 if let Some(size) = self.size {
164 spec.Size = size;
165 }
166 }
167}
168
169pub trait PlotItemStyled: Sized {
174 fn style_mut(&mut self) -> &mut PlotItemStyle;
175
176 fn with_style(mut self, style: PlotItemStyle) -> Self {
178 *self.style_mut() = style;
179 self
180 }
181
182 fn with_line_color(mut self, color: [f32; 4]) -> Self {
184 self.style_mut().line_color = Some(color4(color));
185 self
186 }
187
188 fn with_line_weight(mut self, weight: f32) -> Self {
190 self.style_mut().line_weight = Some(weight);
191 self
192 }
193
194 fn with_fill_color(mut self, color: [f32; 4]) -> Self {
196 self.style_mut().fill_color = Some(color4(color));
197 self
198 }
199
200 fn with_fill_alpha(mut self, alpha: f32) -> Self {
202 self.style_mut().fill_alpha = Some(alpha);
203 self
204 }
205
206 fn with_marker(mut self, marker: crate::Marker) -> Self {
208 self.style_mut().marker = Some(marker as sys::ImPlotMarker);
209 self
210 }
211
212 fn with_marker_size(mut self, size: f32) -> Self {
214 self.style_mut().marker_size = Some(size);
215 self
216 }
217
218 fn with_marker_line_color(mut self, color: [f32; 4]) -> Self {
220 self.style_mut().marker_line_color = Some(color4(color));
221 self
222 }
223
224 fn with_marker_fill_color(mut self, color: [f32; 4]) -> Self {
226 self.style_mut().marker_fill_color = Some(color4(color));
227 self
228 }
229
230 fn with_size(mut self, size: f32) -> Self {
232 self.style_mut().size = Some(size);
233 self
234 }
235}
236
237pub trait Plot {
239 fn plot(&self);
241
242 fn label(&self) -> &str;
244}
245
246pub trait PlotData {
248 fn label(&self) -> &str;
250
251 fn data_len(&self) -> usize;
253
254 fn is_empty(&self) -> bool {
256 self.data_len() == 0
257 }
258
259 fn validate(&self) -> Result<(), PlotError> {
261 if self.is_empty() {
262 Err(PlotError::EmptyData)
263 } else {
264 Ok(())
265 }
266 }
267}
268
269#[derive(Debug, Clone, PartialEq)]
271pub enum PlotError {
272 DataLengthMismatch { x_len: usize, y_len: usize },
274 EmptyData,
276 InvalidData(String),
278 StringConversion(String),
280 PlotCreationFailed(String),
282}
283
284impl std::fmt::Display for PlotError {
285 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
286 match self {
287 PlotError::DataLengthMismatch { x_len, y_len } => {
288 write!(
289 f,
290 "Data length mismatch: x has {} elements, y has {} elements",
291 x_len, y_len
292 )
293 }
294 PlotError::EmptyData => write!(f, "Data is empty"),
295 PlotError::InvalidData(msg) => write!(f, "Invalid data: {}", msg),
296 PlotError::StringConversion(msg) => write!(f, "String conversion error: {}", msg),
297 PlotError::PlotCreationFailed(msg) => write!(f, "Plot creation failed: {}", msg),
298 }
299 }
300}
301
302impl std::error::Error for PlotError {}
303
304pub fn validate_data_lengths<T, U>(data1: &[T], data2: &[U]) -> Result<(), PlotError> {
309 if data1.is_empty() || data2.is_empty() {
310 return Err(PlotError::EmptyData);
311 }
312
313 if data1.len() != data2.len() {
314 return Err(PlotError::DataLengthMismatch {
315 x_len: data1.len(),
316 y_len: data2.len(),
317 });
318 }
319
320 Ok(())
321}
322
323pub(crate) fn with_plot_str<R>(s: &str, f: impl FnOnce(*const c_char) -> R) -> Option<R> {
324 if s.contains('\0') {
325 None
326 } else {
327 Some(with_scratch_txt(s, f))
328 }
329}
330
331pub(crate) fn with_plot_str_or_empty<R>(s: &str, f: impl FnOnce(*const c_char) -> R) -> R {
332 let s = if s.contains('\0') { "" } else { s };
333 with_scratch_txt(s, f)
334}
335
336pub(crate) fn with_plot_str_slice<R>(txts: &[&str], f: impl FnOnce(&[*const c_char]) -> R) -> R {
337 let cleaned: Vec<&str> = txts
338 .iter()
339 .map(|&s| if s.contains('\0') { "" } else { s })
340 .collect();
341 with_scratch_txt_slice(&cleaned, f)
342}
343
344pub(crate) fn with_plot_str_slice_with_opt<R>(
345 txts: &[&str],
346 txt_opt: Option<&str>,
347 f: impl FnOnce(&[*const c_char], *const c_char) -> R,
348) -> R {
349 let cleaned: Vec<&str> = txts
350 .iter()
351 .map(|&s| if s.contains('\0') { "" } else { s })
352 .collect();
353 let txt_opt = txt_opt.filter(|s| !s.contains('\0'));
354 with_scratch_txt_slice_with_opt(&cleaned, txt_opt, f)
355}
356
357pub(crate) fn default_plot_spec() -> sys::ImPlotSpec_c {
358 let auto_col = sys::ImVec4_c {
359 x: 0.0,
360 y: 0.0,
361 z: 0.0,
362 w: -1.0,
363 };
364
365 sys::ImPlotSpec_c {
366 LineColor: auto_col,
367 LineColors: std::ptr::null_mut(),
368 LineWeight: 1.0,
369 FillColor: auto_col,
370 FillColors: std::ptr::null_mut(),
371 FillAlpha: 1.0,
372 Marker: sys::ImPlotMarker_None as _,
373 MarkerSize: 4.0,
374 MarkerSizes: std::ptr::null_mut(),
375 MarkerLineColor: auto_col,
376 MarkerLineColors: std::ptr::null_mut(),
377 MarkerFillColor: auto_col,
378 MarkerFillColors: std::ptr::null_mut(),
379 Size: 4.0,
380 Offset: 0,
381 Stride: crate::IMPLOT_AUTO,
382 Flags: sys::ImPlotItemFlags_None as _,
383 }
384}
385
386pub(crate) fn take_next_plot_spec() -> Option<sys::ImPlotSpec_c> {
387 NEXT_PLOT_SPEC.with(|cell| cell.borrow_mut().take())
388}
389
390pub(crate) fn set_next_plot_spec(spec: Option<sys::ImPlotSpec_c>) {
391 NEXT_PLOT_SPEC.with(|cell| {
392 *cell.borrow_mut() = spec;
393 })
394}
395
396pub(crate) fn plot_spec_from(flags: u32, offset: i32, stride: i32) -> sys::ImPlotSpec_c {
397 let mut spec = take_next_plot_spec().unwrap_or_else(default_plot_spec);
398 spec.Flags = flags as sys::ImPlotItemFlags;
399 spec.Offset = offset;
400 spec.Stride = stride;
401 spec
402}
403
404pub(crate) fn plot_spec_with_style(
405 style: PlotItemStyle,
406 flags: u32,
407 offset: i32,
408 stride: i32,
409) -> sys::ImPlotSpec_c {
410 let mut spec = plot_spec_from(flags, offset, stride);
411 style.apply_to_spec(&mut spec);
412 spec
413}
414
415pub struct PlotBuilder<'a> {
417 plot_type: PlotType<'a>,
418}
419
420pub enum PlotType<'a> {
422 Line {
423 label: &'a str,
424 x_data: &'a [f64],
425 y_data: &'a [f64],
426 },
427 Scatter {
428 label: &'a str,
429 x_data: &'a [f64],
430 y_data: &'a [f64],
431 },
432 Bar {
433 label: &'a str,
434 values: &'a [f64],
435 width: f64,
436 },
437 Histogram {
438 label: &'a str,
439 values: &'a [f64],
440 bins: i32,
441 },
442 Heatmap {
443 label: &'a str,
444 values: &'a [f64],
445 rows: usize,
446 cols: usize,
447 },
448 PieChart {
449 labels: Vec<&'a str>,
450 values: &'a [f64],
451 center: (f64, f64),
452 radius: f64,
453 },
454 Polygon {
455 label: &'a str,
456 x_data: &'a [f64],
457 y_data: &'a [f64],
458 },
459}
460
461impl<'a> PlotBuilder<'a> {
462 pub fn line(label: &'a str, x_data: &'a [f64], y_data: &'a [f64]) -> Self {
464 Self {
465 plot_type: PlotType::Line {
466 label,
467 x_data,
468 y_data,
469 },
470 }
471 }
472
473 pub fn scatter(label: &'a str, x_data: &'a [f64], y_data: &'a [f64]) -> Self {
475 Self {
476 plot_type: PlotType::Scatter {
477 label,
478 x_data,
479 y_data,
480 },
481 }
482 }
483
484 pub fn bar(label: &'a str, values: &'a [f64]) -> Self {
486 Self {
487 plot_type: PlotType::Bar {
488 label,
489 values,
490 width: 0.67,
491 },
492 }
493 }
494
495 pub fn histogram(label: &'a str, values: &'a [f64]) -> Self {
497 Self {
498 plot_type: PlotType::Histogram {
499 label,
500 values,
501 bins: crate::BinMethod::Sturges as i32,
502 },
503 }
504 }
505
506 pub fn heatmap(label: &'a str, values: &'a [f64], rows: usize, cols: usize) -> Self {
508 Self {
509 plot_type: PlotType::Heatmap {
510 label,
511 values,
512 rows,
513 cols,
514 },
515 }
516 }
517
518 pub fn pie_chart(
520 labels: Vec<&'a str>,
521 values: &'a [f64],
522 center: (f64, f64),
523 radius: f64,
524 ) -> Self {
525 Self {
526 plot_type: PlotType::PieChart {
527 labels,
528 values,
529 center,
530 radius,
531 },
532 }
533 }
534
535 pub fn polygon(label: &'a str, x_data: &'a [f64], y_data: &'a [f64]) -> Self {
537 Self {
538 plot_type: PlotType::Polygon {
539 label,
540 x_data,
541 y_data,
542 },
543 }
544 }
545
546 pub fn build(self) -> Result<(), PlotError> {
548 match self.plot_type {
549 PlotType::Line {
550 label,
551 x_data,
552 y_data,
553 } => {
554 let plot = line::LinePlot::new(label, x_data, y_data);
555 plot.validate()?;
556 plot.plot();
557 }
558 PlotType::Scatter {
559 label,
560 x_data,
561 y_data,
562 } => {
563 let plot = scatter::ScatterPlot::new(label, x_data, y_data);
564 plot.validate()?;
565 plot.plot();
566 }
567 PlotType::Bar {
568 label,
569 values,
570 width,
571 } => {
572 let plot = bar::BarPlot::new(label, values).with_bar_size(width);
573 plot.validate()?;
574 plot.plot();
575 }
576 PlotType::Histogram {
577 label,
578 values,
579 bins,
580 } => {
581 let plot = histogram::HistogramPlot::new(label, values).with_bins(bins);
582 plot.validate()?;
583 plot.plot();
584 }
585 PlotType::Heatmap {
586 label,
587 values,
588 rows,
589 cols,
590 } => {
591 let plot = heatmap::HeatmapPlot::new(label, values, rows, cols);
592 plot.validate()?;
593 plot.plot();
594 }
595 PlotType::PieChart {
596 labels,
597 values,
598 center,
599 radius,
600 } => {
601 let plot = pie::PieChartPlot::new(labels, values, center.0, center.1, radius);
602 plot.validate()?;
603 plot.plot();
604 }
605 PlotType::Polygon {
606 label,
607 x_data,
608 y_data,
609 } => {
610 let plot = polygon::PolygonPlot::new(label, x_data, y_data);
611 plot.validate()?;
612 plot.plot();
613 }
614 }
615 Ok(())
616 }
617}