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
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
57pub struct PlotDataOffset(i32);
58
59impl PlotDataOffset {
60 pub const ZERO: Self = Self(0);
62
63 #[inline]
65 pub const fn samples(offset: i32) -> Self {
66 Self(offset)
67 }
68
69 #[inline]
70 pub(crate) const fn raw(self) -> i32 {
71 self.0
72 }
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80pub struct PlotDataStride(i32);
81
82impl PlotDataStride {
83 pub const AUTO: Self = Self(crate::IMPLOT_AUTO);
85
86 #[inline]
90 pub fn bytes(bytes: usize) -> Self {
91 assert!(
92 bytes > 0,
93 "PlotDataStride::bytes() requires a non-zero stride"
94 );
95 let bytes = i32::try_from(bytes)
96 .expect("PlotDataStride::bytes() stride exceeded ImPlot's int range");
97 Self(bytes)
98 }
99
100 #[inline]
102 pub fn for_type<T>() -> Self {
103 Self::bytes(std::mem::size_of::<T>())
104 }
105
106 #[inline]
107 pub(crate) const fn raw(self) -> i32 {
108 self.0
109 }
110}
111
112impl Default for PlotDataStride {
113 fn default() -> Self {
114 Self::AUTO
115 }
116}
117
118#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
120pub struct PlotDataLayout {
121 offset: PlotDataOffset,
122 stride: PlotDataStride,
123}
124
125impl PlotDataLayout {
126 pub const DEFAULT: Self = Self {
128 offset: PlotDataOffset::ZERO,
129 stride: PlotDataStride::AUTO,
130 };
131
132 #[inline]
134 pub const fn new(offset: PlotDataOffset, stride: PlotDataStride) -> Self {
135 Self { offset, stride }
136 }
137
138 #[inline]
140 pub const fn with_offset(mut self, offset: PlotDataOffset) -> Self {
141 self.offset = offset;
142 self
143 }
144
145 #[inline]
147 pub const fn with_stride(mut self, stride: PlotDataStride) -> Self {
148 self.stride = stride;
149 self
150 }
151
152 #[inline]
153 pub(crate) const fn raw_offset(self) -> i32 {
154 self.offset.raw()
155 }
156
157 #[inline]
158 pub(crate) const fn raw_stride(self) -> i32 {
159 self.stride.raw()
160 }
161}
162
163fn color4(rgba: [f32; 4]) -> sys::ImVec4_c {
164 sys::ImVec4_c {
165 x: rgba[0],
166 y: rgba[1],
167 z: rgba[2],
168 w: rgba[3],
169 }
170}
171
172#[derive(Debug, Clone, Copy, Default, PartialEq)]
177pub struct PlotItemStyle {
178 pub(crate) line_color: Option<sys::ImVec4_c>,
179 pub(crate) line_weight: Option<f32>,
180 pub(crate) fill_color: Option<sys::ImVec4_c>,
181 pub(crate) fill_alpha: Option<f32>,
182 pub(crate) marker: Option<sys::ImPlotMarker>,
183 pub(crate) marker_size: Option<f32>,
184 pub(crate) marker_line_color: Option<sys::ImVec4_c>,
185 pub(crate) marker_fill_color: Option<sys::ImVec4_c>,
186 pub(crate) size: Option<f32>,
187}
188
189impl PlotItemStyle {
190 pub fn new() -> Self {
192 Self::default()
193 }
194
195 pub fn with_line_color(mut self, color: [f32; 4]) -> Self {
197 self.line_color = Some(color4(color));
198 self
199 }
200
201 pub fn with_line_weight(mut self, weight: f32) -> Self {
203 self.line_weight = Some(weight);
204 self
205 }
206
207 pub fn with_fill_color(mut self, color: [f32; 4]) -> Self {
209 self.fill_color = Some(color4(color));
210 self
211 }
212
213 pub fn with_fill_alpha(mut self, alpha: f32) -> Self {
215 self.fill_alpha = Some(alpha);
216 self
217 }
218
219 pub fn with_marker(mut self, marker: crate::Marker) -> Self {
221 self.marker = Some(marker as sys::ImPlotMarker);
222 self
223 }
224
225 pub fn with_marker_size(mut self, size: f32) -> Self {
227 self.marker_size = Some(size);
228 self
229 }
230
231 pub fn with_marker_line_color(mut self, color: [f32; 4]) -> Self {
233 self.marker_line_color = Some(color4(color));
234 self
235 }
236
237 pub fn with_marker_fill_color(mut self, color: [f32; 4]) -> Self {
239 self.marker_fill_color = Some(color4(color));
240 self
241 }
242
243 pub fn with_size(mut self, size: f32) -> Self {
245 self.size = Some(size);
246 self
247 }
248
249 pub(crate) fn apply_to_spec(self, spec: &mut sys::ImPlotSpec_c) {
250 if let Some(line_color) = self.line_color {
251 spec.LineColor = line_color;
252 }
253 if let Some(line_weight) = self.line_weight {
254 spec.LineWeight = line_weight;
255 }
256 if let Some(fill_color) = self.fill_color {
257 spec.FillColor = fill_color;
258 }
259 if let Some(fill_alpha) = self.fill_alpha {
260 spec.FillAlpha = fill_alpha;
261 }
262 if let Some(marker) = self.marker {
263 spec.Marker = marker;
264 }
265 if let Some(marker_size) = self.marker_size {
266 spec.MarkerSize = marker_size;
267 }
268 if let Some(marker_line_color) = self.marker_line_color {
269 spec.MarkerLineColor = marker_line_color;
270 }
271 if let Some(marker_fill_color) = self.marker_fill_color {
272 spec.MarkerFillColor = marker_fill_color;
273 }
274 if let Some(size) = self.size {
275 spec.Size = size;
276 }
277 }
278}
279
280pub trait PlotItemStyled: Sized {
285 fn style_mut(&mut self) -> &mut PlotItemStyle;
286
287 fn with_style(mut self, style: PlotItemStyle) -> Self {
289 *self.style_mut() = style;
290 self
291 }
292
293 fn with_line_color(mut self, color: [f32; 4]) -> Self {
295 self.style_mut().line_color = Some(color4(color));
296 self
297 }
298
299 fn with_line_weight(mut self, weight: f32) -> Self {
301 self.style_mut().line_weight = Some(weight);
302 self
303 }
304
305 fn with_fill_color(mut self, color: [f32; 4]) -> Self {
307 self.style_mut().fill_color = Some(color4(color));
308 self
309 }
310
311 fn with_fill_alpha(mut self, alpha: f32) -> Self {
313 self.style_mut().fill_alpha = Some(alpha);
314 self
315 }
316
317 fn with_marker(mut self, marker: crate::Marker) -> Self {
319 self.style_mut().marker = Some(marker as sys::ImPlotMarker);
320 self
321 }
322
323 fn with_marker_size(mut self, size: f32) -> Self {
325 self.style_mut().marker_size = Some(size);
326 self
327 }
328
329 fn with_marker_line_color(mut self, color: [f32; 4]) -> Self {
331 self.style_mut().marker_line_color = Some(color4(color));
332 self
333 }
334
335 fn with_marker_fill_color(mut self, color: [f32; 4]) -> Self {
337 self.style_mut().marker_fill_color = Some(color4(color));
338 self
339 }
340
341 fn with_size(mut self, size: f32) -> Self {
343 self.style_mut().size = Some(size);
344 self
345 }
346}
347
348pub trait Plot {
350 fn plot(&self, plot_ui: &crate::PlotUi<'_>);
352
353 fn label(&self) -> &str;
355}
356
357pub trait PlotData {
359 fn label(&self) -> &str;
361
362 fn data_len(&self) -> usize;
364
365 fn is_empty(&self) -> bool {
367 self.data_len() == 0
368 }
369
370 fn validate(&self) -> Result<(), PlotError> {
372 if self.is_empty() {
373 Err(PlotError::EmptyData)
374 } else {
375 Ok(())
376 }
377 }
378}
379
380#[derive(Debug, Clone, PartialEq)]
382pub enum PlotError {
383 DataLengthMismatch { x_len: usize, y_len: usize },
385 EmptyData,
387 InvalidData(String),
389 StringConversion(String),
391 PlotCreationFailed(String),
393}
394
395impl std::fmt::Display for PlotError {
396 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
397 match self {
398 PlotError::DataLengthMismatch { x_len, y_len } => {
399 write!(
400 f,
401 "Data length mismatch: x has {} elements, y has {} elements",
402 x_len, y_len
403 )
404 }
405 PlotError::EmptyData => write!(f, "Data is empty"),
406 PlotError::InvalidData(msg) => write!(f, "Invalid data: {}", msg),
407 PlotError::StringConversion(msg) => write!(f, "String conversion error: {}", msg),
408 PlotError::PlotCreationFailed(msg) => write!(f, "Plot creation failed: {}", msg),
409 }
410 }
411}
412
413impl std::error::Error for PlotError {}
414
415pub fn validate_data_lengths<T, U>(data1: &[T], data2: &[U]) -> Result<(), PlotError> {
420 if data1.is_empty() || data2.is_empty() {
421 return Err(PlotError::EmptyData);
422 }
423
424 if data1.len() != data2.len() {
425 return Err(PlotError::DataLengthMismatch {
426 x_len: data1.len(),
427 y_len: data2.len(),
428 });
429 }
430
431 Ok(())
432}
433
434pub(crate) fn with_plot_str<R>(s: &str, f: impl FnOnce(*const c_char) -> R) -> Option<R> {
435 if s.contains('\0') {
436 None
437 } else {
438 Some(with_scratch_txt(s, f))
439 }
440}
441
442pub(crate) fn with_plot_str_or_empty<R>(s: &str, f: impl FnOnce(*const c_char) -> R) -> R {
443 let s = if s.contains('\0') { "" } else { s };
444 with_scratch_txt(s, f)
445}
446
447pub(crate) fn with_plot_str_slice<R>(txts: &[&str], f: impl FnOnce(&[*const c_char]) -> R) -> R {
448 let cleaned: Vec<&str> = txts
449 .iter()
450 .map(|&s| if s.contains('\0') { "" } else { s })
451 .collect();
452 with_scratch_txt_slice(&cleaned, f)
453}
454
455pub(crate) fn with_plot_str_slice_with_opt<R>(
456 txts: &[&str],
457 txt_opt: Option<&str>,
458 f: impl FnOnce(&[*const c_char], *const c_char) -> R,
459) -> R {
460 let cleaned: Vec<&str> = txts
461 .iter()
462 .map(|&s| if s.contains('\0') { "" } else { s })
463 .collect();
464 let txt_opt = txt_opt.filter(|s| !s.contains('\0'));
465 with_scratch_txt_slice_with_opt(&cleaned, txt_opt, f)
466}
467
468pub(crate) fn default_plot_spec() -> sys::ImPlotSpec_c {
469 let auto_col = sys::ImVec4_c {
470 x: 0.0,
471 y: 0.0,
472 z: 0.0,
473 w: -1.0,
474 };
475
476 sys::ImPlotSpec_c {
477 LineColor: auto_col,
478 LineColors: std::ptr::null_mut(),
479 LineWeight: 1.0,
480 FillColor: auto_col,
481 FillColors: std::ptr::null_mut(),
482 FillAlpha: 1.0,
483 Marker: sys::ImPlotMarker_None as _,
484 MarkerSize: 4.0,
485 MarkerSizes: std::ptr::null_mut(),
486 MarkerLineColor: auto_col,
487 MarkerLineColors: std::ptr::null_mut(),
488 MarkerFillColor: auto_col,
489 MarkerFillColors: std::ptr::null_mut(),
490 Size: 4.0,
491 Offset: 0,
492 Stride: crate::IMPLOT_AUTO,
493 Flags: sys::ImPlotItemFlags_None as _,
494 }
495}
496
497pub(crate) fn take_next_plot_spec() -> Option<sys::ImPlotSpec_c> {
498 NEXT_PLOT_SPEC.with(|cell| cell.borrow_mut().take())
499}
500
501pub(crate) fn set_next_plot_spec(spec: Option<sys::ImPlotSpec_c>) {
502 NEXT_PLOT_SPEC.with(|cell| {
503 *cell.borrow_mut() = spec;
504 })
505}
506
507pub(crate) fn plot_spec_from(flags: u32, layout: PlotDataLayout) -> sys::ImPlotSpec_c {
508 let mut spec = take_next_plot_spec().unwrap_or_else(default_plot_spec);
509 spec.Flags = flags as sys::ImPlotItemFlags;
510 spec.Offset = layout.raw_offset();
511 spec.Stride = layout.raw_stride();
512 spec
513}
514
515pub(crate) fn plot_spec_with_style(
516 style: PlotItemStyle,
517 flags: u32,
518 layout: PlotDataLayout,
519) -> sys::ImPlotSpec_c {
520 let mut spec = plot_spec_from(flags, layout);
521 style.apply_to_spec(&mut spec);
522 spec
523}
524
525#[cfg(test)]
526mod layout_tests {
527 use super::{PlotDataLayout, PlotDataOffset, PlotDataStride, plot_spec_from};
528
529 #[test]
530 fn data_layout_allows_signed_sample_offsets() {
531 let layout = PlotDataLayout::DEFAULT.with_offset(PlotDataOffset::samples(-8));
532 let spec = plot_spec_from(0, layout);
533 assert_eq!(spec.Offset, -8);
534 assert_eq!(spec.Stride, crate::IMPLOT_AUTO);
535 }
536
537 #[test]
538 fn data_stride_bytes_are_positive() {
539 let stride = PlotDataStride::bytes(16);
540 let layout = PlotDataLayout::DEFAULT.with_stride(stride);
541 let spec = plot_spec_from(0, layout);
542 assert_eq!(spec.Stride, 16);
543 }
544
545 #[test]
546 #[should_panic(expected = "requires a non-zero stride")]
547 fn zero_data_stride_panics_before_ffi() {
548 let _ = PlotDataStride::bytes(0);
549 }
550}
551
552pub struct PlotBuilder<'a> {
554 plot_type: PlotType<'a>,
555}
556
557pub enum PlotType<'a> {
559 Line {
560 label: &'a str,
561 x_data: &'a [f64],
562 y_data: &'a [f64],
563 },
564 Scatter {
565 label: &'a str,
566 x_data: &'a [f64],
567 y_data: &'a [f64],
568 },
569 Bar {
570 label: &'a str,
571 values: &'a [f64],
572 width: f64,
573 },
574 Histogram {
575 label: &'a str,
576 values: &'a [f64],
577 bins: crate::HistogramBins,
578 },
579 Heatmap {
580 label: &'a str,
581 values: &'a [f64],
582 rows: usize,
583 cols: usize,
584 },
585 PieChart {
586 labels: Vec<&'a str>,
587 values: &'a [f64],
588 center: (f64, f64),
589 radius: f64,
590 },
591 Polygon {
592 label: &'a str,
593 x_data: &'a [f64],
594 y_data: &'a [f64],
595 },
596}
597
598impl<'a> PlotBuilder<'a> {
599 pub fn line(label: &'a str, x_data: &'a [f64], y_data: &'a [f64]) -> Self {
601 Self {
602 plot_type: PlotType::Line {
603 label,
604 x_data,
605 y_data,
606 },
607 }
608 }
609
610 pub fn scatter(label: &'a str, x_data: &'a [f64], y_data: &'a [f64]) -> Self {
612 Self {
613 plot_type: PlotType::Scatter {
614 label,
615 x_data,
616 y_data,
617 },
618 }
619 }
620
621 pub fn bar(label: &'a str, values: &'a [f64]) -> Self {
623 Self {
624 plot_type: PlotType::Bar {
625 label,
626 values,
627 width: 0.67,
628 },
629 }
630 }
631
632 pub fn histogram(label: &'a str, values: &'a [f64]) -> Self {
634 Self {
635 plot_type: PlotType::Histogram {
636 label,
637 values,
638 bins: crate::HistogramBins::DEFAULT,
639 },
640 }
641 }
642
643 pub fn heatmap(label: &'a str, values: &'a [f64], rows: usize, cols: usize) -> Self {
645 Self {
646 plot_type: PlotType::Heatmap {
647 label,
648 values,
649 rows,
650 cols,
651 },
652 }
653 }
654
655 pub fn pie_chart(
657 labels: Vec<&'a str>,
658 values: &'a [f64],
659 center: (f64, f64),
660 radius: f64,
661 ) -> Self {
662 Self {
663 plot_type: PlotType::PieChart {
664 labels,
665 values,
666 center,
667 radius,
668 },
669 }
670 }
671
672 pub fn polygon(label: &'a str, x_data: &'a [f64], y_data: &'a [f64]) -> Self {
674 Self {
675 plot_type: PlotType::Polygon {
676 label,
677 x_data,
678 y_data,
679 },
680 }
681 }
682
683 pub fn build(self, plot_ui: &crate::PlotUi<'_>) -> Result<(), PlotError> {
685 match self.plot_type {
686 PlotType::Line {
687 label,
688 x_data,
689 y_data,
690 } => {
691 let plot = line::LinePlot::new(label, x_data, y_data);
692 plot.validate()?;
693 plot.plot(plot_ui);
694 }
695 PlotType::Scatter {
696 label,
697 x_data,
698 y_data,
699 } => {
700 let plot = scatter::ScatterPlot::new(label, x_data, y_data);
701 plot.validate()?;
702 plot.plot(plot_ui);
703 }
704 PlotType::Bar {
705 label,
706 values,
707 width,
708 } => {
709 let plot = bar::BarPlot::new(label, values).with_bar_size(width);
710 plot.validate()?;
711 plot.plot(plot_ui);
712 }
713 PlotType::Histogram {
714 label,
715 values,
716 bins,
717 } => {
718 let plot = histogram::HistogramPlot::new(label, values).with_bins(bins);
719 plot.validate()?;
720 plot.plot(plot_ui);
721 }
722 PlotType::Heatmap {
723 label,
724 values,
725 rows,
726 cols,
727 } => {
728 let plot = heatmap::HeatmapPlot::new(label, values, rows, cols);
729 plot.validate()?;
730 plot.plot(plot_ui);
731 }
732 PlotType::PieChart {
733 labels,
734 values,
735 center,
736 radius,
737 } => {
738 let plot = pie::PieChartPlot::new(labels, values, center.0, center.1, radius);
739 plot.validate()?;
740 plot.plot(plot_ui);
741 }
742 PlotType::Polygon {
743 label,
744 x_data,
745 y_data,
746 } => {
747 let plot = polygon::PolygonPlot::new(label, x_data, y_data);
748 plot.validate()?;
749 plot.plot(plot_ui);
750 }
751 }
752 Ok(())
753 }
754}