dear_implot/
advanced.rs

1//! Advanced plotting features for complex visualizations
2//!
3//! This module provides high-level functionality for creating complex plots
4//! with multiple subplots, legends, and advanced layout management.
5
6use crate::{AxisFlags, plots::PlotError, sys};
7use std::ffi::CString;
8use std::marker::PhantomData;
9
10/// Multi-plot layout manager for creating subplot grids
11pub struct SubplotGrid<'a> {
12    title: &'a str,
13    rows: i32,
14    cols: i32,
15    size: Option<[f32; 2]>,
16    flags: SubplotFlags,
17    row_ratios: Option<&'a [f32]>,
18    col_ratios: Option<&'a [f32]>,
19}
20
21bitflags::bitflags! {
22    /// Flags for subplot configuration
23    pub struct SubplotFlags: u32 {
24        const NONE = 0;
25        const NO_TITLE = 1 << 0;
26        const NO_RESIZE = 1 << 1;
27        const NO_ALIGN = 1 << 2;
28        const SHARE_ITEMS = 1 << 3;
29        const LINK_ROWS = 1 << 4;
30        const LINK_COLS = 1 << 5;
31        const LINK_ALL_X = 1 << 6;
32        const LINK_ALL_Y = 1 << 7;
33        const COLUMN_MAJOR = 1 << 8;
34    }
35}
36
37impl<'a> SubplotGrid<'a> {
38    /// Create a new subplot grid
39    pub fn new(title: &'a str, rows: i32, cols: i32) -> Self {
40        Self {
41            title,
42            rows,
43            cols,
44            size: None,
45            flags: SubplotFlags::NONE,
46            row_ratios: None,
47            col_ratios: None,
48        }
49    }
50
51    /// Set the size of the subplot grid
52    pub fn with_size(mut self, size: [f32; 2]) -> Self {
53        self.size = Some(size);
54        self
55    }
56
57    /// Set subplot flags
58    pub fn with_flags(mut self, flags: SubplotFlags) -> Self {
59        self.flags = flags;
60        self
61    }
62
63    /// Set row height ratios
64    pub fn with_row_ratios(mut self, ratios: &'a [f32]) -> Self {
65        self.row_ratios = Some(ratios);
66        self
67    }
68
69    /// Set column width ratios
70    pub fn with_col_ratios(mut self, ratios: &'a [f32]) -> Self {
71        self.col_ratios = Some(ratios);
72        self
73    }
74
75    /// Begin the subplot grid and return a token
76    pub fn begin(self) -> Result<SubplotToken<'a>, PlotError> {
77        let title_cstr =
78            CString::new(self.title).map_err(|e| PlotError::StringConversion(e.to_string()))?;
79
80        let size = self.size.unwrap_or([-1.0, -1.0]);
81        let size_vec = sys::ImVec2_c {
82            x: size[0],
83            y: size[1],
84        };
85
86        let row_ratios_ptr = self
87            .row_ratios
88            .map(|r| r.as_ptr() as *mut f32)
89            .unwrap_or(std::ptr::null_mut());
90
91        let col_ratios_ptr = self
92            .col_ratios
93            .map(|c| c.as_ptr() as *mut f32)
94            .unwrap_or(std::ptr::null_mut());
95
96        let success = unsafe {
97            sys::ImPlot_BeginSubplots(
98                title_cstr.as_ptr(),
99                self.rows,
100                self.cols,
101                size_vec,
102                self.flags.bits() as i32,
103                row_ratios_ptr,
104                col_ratios_ptr,
105            )
106        };
107
108        if success {
109            Ok(SubplotToken {
110                _title: title_cstr,
111                _phantom: PhantomData,
112            })
113        } else {
114            Err(PlotError::PlotCreationFailed(
115                "Failed to begin subplots".to_string(),
116            ))
117        }
118    }
119}
120
121/// Token representing an active subplot grid
122pub struct SubplotToken<'a> {
123    _title: CString,
124    _phantom: PhantomData<&'a ()>,
125}
126
127impl<'a> SubplotToken<'a> {
128    /// End the subplot grid
129    pub fn end(self) {
130        unsafe {
131            sys::ImPlot_EndSubplots();
132        }
133    }
134}
135
136impl<'a> Drop for SubplotToken<'a> {
137    fn drop(&mut self) {
138        unsafe {
139            sys::ImPlot_EndSubplots();
140        }
141    }
142}
143
144/// Multi-axis plot support
145pub struct MultiAxisPlot<'a> {
146    title: &'a str,
147    size: Option<[f32; 2]>,
148    y_axes: Vec<YAxisConfig<'a>>,
149}
150
151/// Configuration for a Y-axis
152pub struct YAxisConfig<'a> {
153    pub label: Option<&'a str>,
154    pub flags: AxisFlags,
155    pub range: Option<(f64, f64)>,
156}
157
158impl<'a> MultiAxisPlot<'a> {
159    /// Create a new multi-axis plot
160    pub fn new(title: &'a str) -> Self {
161        Self {
162            title,
163            size: None,
164            y_axes: Vec::new(),
165        }
166    }
167
168    /// Set the plot size
169    pub fn with_size(mut self, size: [f32; 2]) -> Self {
170        self.size = Some(size);
171        self
172    }
173
174    /// Add a Y-axis
175    pub fn add_y_axis(mut self, config: YAxisConfig<'a>) -> Self {
176        self.y_axes.push(config);
177        self
178    }
179
180    /// Begin the multi-axis plot
181    pub fn begin(self) -> Result<MultiAxisToken<'a>, PlotError> {
182        let title_cstr =
183            CString::new(self.title).map_err(|e| PlotError::StringConversion(e.to_string()))?;
184
185        let size = self.size.unwrap_or([-1.0, -1.0]);
186        let size_vec = sys::ImVec2_c {
187            x: size[0],
188            y: size[1],
189        };
190
191        let success = unsafe { sys::ImPlot_BeginPlot(title_cstr.as_ptr(), size_vec, 0) };
192
193        if success {
194            // Setup additional Y-axes
195            for (i, axis_config) in self.y_axes.iter().enumerate() {
196                if i > 0 {
197                    // Skip the first axis (it's the default)
198                    let label_cstr = if let Some(label) = axis_config.label {
199                        Some(
200                            CString::new(label)
201                                .map_err(|e| PlotError::StringConversion(e.to_string()))?,
202                        )
203                    } else {
204                        None
205                    };
206
207                    let label_ptr = label_cstr
208                        .as_ref()
209                        .map(|cstr| cstr.as_ptr())
210                        .unwrap_or(std::ptr::null());
211
212                    unsafe {
213                        let axis_enum = (i as i32) + 3; // ImAxis_Y1 = 3
214                        sys::ImPlot_SetupAxis(
215                            axis_enum,
216                            label_ptr,
217                            axis_config.flags.bits() as i32,
218                        );
219
220                        if let Some((min, max)) = axis_config.range {
221                            sys::ImPlot_SetupAxisLimits(axis_enum, min, max, 0);
222                        }
223                    }
224                }
225            }
226
227            Ok(MultiAxisToken {
228                _title: title_cstr,
229                _axis_labels: self
230                    .y_axes
231                    .into_iter()
232                    .filter_map(|config| config.label)
233                    .map(|label| CString::new(label).unwrap())
234                    .collect(),
235                _phantom: PhantomData,
236            })
237        } else {
238            Err(PlotError::PlotCreationFailed(
239                "Failed to begin multi-axis plot".to_string(),
240            ))
241        }
242    }
243}
244
245/// Token representing an active multi-axis plot
246pub struct MultiAxisToken<'a> {
247    _title: CString,
248    _axis_labels: Vec<CString>,
249    _phantom: PhantomData<&'a ()>,
250}
251
252impl<'a> MultiAxisToken<'a> {
253    /// Set the current Y-axis for subsequent plots
254    pub fn set_y_axis(&self, axis: i32) {
255        unsafe {
256            sys::ImPlot_SetAxes(
257                0,        // ImAxis_X1
258                axis + 3, // ImAxis_Y1 = 3
259            );
260        }
261    }
262
263    /// End the multi-axis plot
264    pub fn end(self) {
265        unsafe {
266            sys::ImPlot_EndPlot();
267        }
268    }
269}
270
271impl<'a> Drop for MultiAxisToken<'a> {
272    fn drop(&mut self) {
273        unsafe {
274            sys::ImPlot_EndPlot();
275        }
276    }
277}
278
279/// Legend management utilities
280pub struct LegendManager;
281
282impl LegendManager {
283    /// Setup legend with custom position and flags
284    pub fn setup(location: LegendLocation, flags: LegendFlags) {
285        unsafe {
286            sys::ImPlot_SetupLegend(location as i32, flags.bits() as i32);
287        }
288    }
289
290    /// Begin a custom legend
291    pub fn begin_custom(label: &str, _size: [f32; 2]) -> Result<LegendToken, PlotError> {
292        let label_cstr =
293            CString::new(label).map_err(|e| PlotError::StringConversion(e.to_string()))?;
294
295        let success = unsafe {
296            sys::ImPlot_BeginLegendPopup(
297                label_cstr.as_ptr(),
298                1, // mouse button
299            )
300        };
301
302        if success {
303            Ok(LegendToken { _label: label_cstr })
304        } else {
305            Err(PlotError::PlotCreationFailed(
306                "Failed to begin legend".to_string(),
307            ))
308        }
309    }
310}
311
312/// Legend location options
313#[repr(i32)]
314pub enum LegendLocation {
315    Center = 0,
316    North = 1,
317    South = 2,
318    West = 4,
319    East = 8,
320    NorthWest = 5,
321    NorthEast = 9,
322    SouthWest = 6,
323    SouthEast = 10,
324}
325
326bitflags::bitflags! {
327    /// Flags for legend configuration
328    pub struct LegendFlags: u32 {
329        const NONE = 0;
330        const NO_BUTTONS = 1 << 0;
331        const NO_HIGHLIGHT_ITEM = 1 << 1;
332        const NO_HIGHLIGHT_AXIS = 1 << 2;
333        const NO_MENUS = 1 << 3;
334        const OUTSIDE = 1 << 4;
335        const HORIZONTAL = 1 << 5;
336        const SORT = 1 << 6;
337    }
338}
339
340/// Token representing an active legend
341pub struct LegendToken {
342    _label: CString,
343}
344
345impl LegendToken {
346    /// End the legend
347    pub fn end(self) {
348        unsafe {
349            sys::ImPlot_EndLegendPopup();
350        }
351    }
352}
353
354impl Drop for LegendToken {
355    fn drop(&mut self) {
356        unsafe {
357            sys::ImPlot_EndLegendPopup();
358        }
359    }
360}