Skip to main content

dear_implot/context/
ui.rs

1use super::core::PlotContext;
2use super::token::PlotToken;
3use super::validation::assert_finite_vec2;
4use crate::{XAxis, YAxis, sys};
5use dear_imgui_rs::{Ui, with_scratch_txt};
6
7/// A temporary reference for building plots
8///
9/// This struct ensures that plots can only be created when both ImGui and ImPlot
10/// contexts are available and properly set up.
11pub struct PlotUi<'ui> {
12    #[allow(dead_code)]
13    pub(crate) context: &'ui PlotContext,
14    #[allow(dead_code)]
15    pub(crate) ui: &'ui Ui,
16}
17
18impl<'ui> PlotUi<'ui> {
19    #[inline]
20    pub(crate) fn bind(&self) {
21        self.context.assert_imgui_alive();
22        self.context.binding().bind("dear-implot: PlotUi");
23    }
24
25    /// Begin a new plot with the given title
26    ///
27    /// Returns a PlotToken if the plot was successfully started.
28    /// The plot will be automatically ended when the token is dropped.
29    pub fn begin_plot(&self, title: &str) -> Option<PlotToken<'_>> {
30        let size = sys::ImVec2_c { x: -1.0, y: 0.0 };
31        if title.contains('\0') {
32            return None;
33        }
34        self.bind();
35        let started = with_scratch_txt(title, |ptr| unsafe { sys::ImPlot_BeginPlot(ptr, size, 0) });
36
37        if started {
38            Some(PlotToken::new(
39                self.context.binding(),
40                self.context.imgui_alive_token(),
41            ))
42        } else {
43            None
44        }
45    }
46
47    /// Begin a plot with custom size
48    pub fn begin_plot_with_size(&self, title: &str, size: [f32; 2]) -> Option<PlotToken<'_>> {
49        assert_finite_vec2("PlotUi::begin_plot_with_size()", "size", size);
50        let plot_size = sys::ImVec2_c {
51            x: size[0],
52            y: size[1],
53        };
54        if title.contains('\0') {
55            return None;
56        }
57        self.bind();
58        let started = with_scratch_txt(title, |ptr| unsafe {
59            sys::ImPlot_BeginPlot(ptr, plot_size, 0)
60        });
61
62        if started {
63            Some(PlotToken::new(
64                self.context.binding(),
65                self.context.imgui_alive_token(),
66            ))
67        } else {
68            None
69        }
70    }
71
72    /// Plot a line with the given label and data
73    ///
74    /// This is a convenience method that can be called within a plot.
75    pub fn plot_line(&self, label: &str, x_data: &[f64], y_data: &[f64]) {
76        if x_data.len() != y_data.len() {
77            return; // Data length mismatch
78        }
79        let count = match i32::try_from(x_data.len()) {
80            Ok(v) => v,
81            Err(_) => return,
82        };
83
84        let label = if label.contains('\0') { "" } else { label };
85        self.bind();
86        with_scratch_txt(label, |ptr| unsafe {
87            let spec = crate::plots::plot_spec_from(0, crate::plots::PlotDataLayout::DEFAULT);
88            sys::ImPlot_PlotLine_doublePtrdoublePtr(
89                ptr,
90                x_data.as_ptr(),
91                y_data.as_ptr(),
92                count,
93                spec,
94            );
95        })
96    }
97
98    /// Plot a scatter plot with the given label and data
99    pub fn plot_scatter(&self, label: &str, x_data: &[f64], y_data: &[f64]) {
100        if x_data.len() != y_data.len() {
101            return; // Data length mismatch
102        }
103        let count = match i32::try_from(x_data.len()) {
104            Ok(v) => v,
105            Err(_) => return,
106        };
107
108        let label = if label.contains('\0') { "" } else { label };
109        self.bind();
110        with_scratch_txt(label, |ptr| unsafe {
111            let spec = crate::plots::plot_spec_from(0, crate::plots::PlotDataLayout::DEFAULT);
112            sys::ImPlot_PlotScatter_doublePtrdoublePtr(
113                ptr,
114                x_data.as_ptr(),
115                y_data.as_ptr(),
116                count,
117                spec,
118            );
119        })
120    }
121
122    /// Plot a polygon with the given label and vertex data.
123    pub fn plot_polygon(&self, label: &str, x_data: &[f64], y_data: &[f64]) {
124        if x_data.len() != y_data.len() {
125            return;
126        }
127        let count = match i32::try_from(x_data.len()) {
128            Ok(v) => v,
129            Err(_) => return,
130        };
131
132        let label = if label.contains('\0') { "" } else { label };
133        self.bind();
134        with_scratch_txt(label, |ptr| unsafe {
135            let spec = crate::plots::plot_spec_from(0, crate::plots::PlotDataLayout::DEFAULT);
136            sys::ImPlot_PlotPolygon_doublePtr(ptr, x_data.as_ptr(), y_data.as_ptr(), count, spec);
137        })
138    }
139
140    /// Check if the plot area is hovered
141    pub fn is_plot_hovered(&self) -> bool {
142        self.bind();
143        unsafe { sys::ImPlot_IsPlotHovered() }
144    }
145
146    /// Get the mouse position in plot coordinates
147    pub fn get_plot_mouse_pos(&self, y_axis: Option<crate::YAxisChoice>) -> sys::ImPlotPoint {
148        let y_axis_i32 = crate::y_axis_choice_option_to_i32(y_axis);
149        let y_axis = match y_axis_i32 {
150            0 => 3,
151            1 => 4,
152            2 => 5,
153            _ => 3,
154        };
155        self.bind();
156        unsafe { sys::ImPlot_GetPlotMousePos(0, y_axis) }
157    }
158
159    /// Get the mouse position in plot coordinates for specific axes
160    pub fn get_plot_mouse_pos_axes(&self, x_axis: XAxis, y_axis: YAxis) -> sys::ImPlotPoint {
161        self.bind();
162        unsafe { sys::ImPlot_GetPlotMousePos(x_axis as i32, y_axis as i32) }
163    }
164
165    /// Set current axes for subsequent plot submissions
166    pub fn set_axes(&self, x_axis: XAxis, y_axis: YAxis) {
167        self.bind();
168        unsafe { sys::ImPlot_SetAxes(x_axis as i32, y_axis as i32) }
169    }
170}