1use crate::context::PlotScopeGuard;
7use crate::{AxisFlags, plots::PlotError, sys};
8use std::ffi::CString;
9use std::marker::PhantomData;
10
11pub struct SubplotGrid<'a> {
13 title: &'a str,
14 rows: i32,
15 cols: i32,
16 size: Option<[f32; 2]>,
17 flags: SubplotFlags,
18 row_ratios: Option<Vec<f32>>,
19 col_ratios: Option<Vec<f32>>,
20}
21
22bitflags::bitflags! {
23 pub struct SubplotFlags: u32 {
25 const NONE = 0;
26 const NO_TITLE = 1 << 0;
27 const NO_RESIZE = 1 << 1;
28 const NO_ALIGN = 1 << 2;
29 const SHARE_ITEMS = 1 << 3;
30 const LINK_ROWS = 1 << 4;
31 const LINK_COLS = 1 << 5;
32 const LINK_ALL_X = 1 << 6;
33 const LINK_ALL_Y = 1 << 7;
34 const COLUMN_MAJOR = 1 << 8;
35 }
36}
37
38impl<'a> SubplotGrid<'a> {
39 pub fn new(title: &'a str, rows: i32, cols: i32) -> Self {
41 Self {
42 title,
43 rows,
44 cols,
45 size: None,
46 flags: SubplotFlags::NONE,
47 row_ratios: None,
48 col_ratios: None,
49 }
50 }
51
52 pub fn with_size(mut self, size: [f32; 2]) -> Self {
54 self.size = Some(size);
55 self
56 }
57
58 pub fn with_flags(mut self, flags: SubplotFlags) -> Self {
60 self.flags = flags;
61 self
62 }
63
64 pub fn with_row_ratios(mut self, ratios: &[f32]) -> Self {
66 self.row_ratios = if ratios.is_empty() {
67 None
68 } else {
69 Some(ratios.to_vec())
70 };
71 self
72 }
73
74 pub fn with_col_ratios(mut self, ratios: &[f32]) -> Self {
76 self.col_ratios = if ratios.is_empty() {
77 None
78 } else {
79 Some(ratios.to_vec())
80 };
81 self
82 }
83
84 pub fn begin(self) -> Result<SubplotToken<'a>, PlotError> {
86 let title_cstr =
87 CString::new(self.title).map_err(|e| PlotError::StringConversion(e.to_string()))?;
88
89 let size = self.size.unwrap_or([-1.0, -1.0]);
90 let size_vec = sys::ImVec2_c {
91 x: size[0],
92 y: size[1],
93 };
94
95 let mut row_ratios = self.row_ratios;
98 let mut col_ratios = self.col_ratios;
99 let row_ratios_ptr = row_ratios
100 .as_mut()
101 .map(|r| r.as_mut_ptr())
102 .unwrap_or(std::ptr::null_mut());
103 let col_ratios_ptr = col_ratios
104 .as_mut()
105 .map(|c| c.as_mut_ptr())
106 .unwrap_or(std::ptr::null_mut());
107
108 let success = unsafe {
109 sys::ImPlot_BeginSubplots(
110 title_cstr.as_ptr(),
111 self.rows,
112 self.cols,
113 size_vec,
114 self.flags.bits() as i32,
115 row_ratios_ptr,
116 col_ratios_ptr,
117 )
118 };
119
120 if success {
121 Ok(SubplotToken {
122 _title: title_cstr,
123 _row_ratios: row_ratios,
124 _col_ratios: col_ratios,
125 _phantom: PhantomData,
126 })
127 } else {
128 Err(PlotError::PlotCreationFailed(
129 "Failed to begin subplots".to_string(),
130 ))
131 }
132 }
133}
134
135pub struct SubplotToken<'a> {
137 _title: CString,
138 _row_ratios: Option<Vec<f32>>,
139 _col_ratios: Option<Vec<f32>>,
140 _phantom: PhantomData<&'a ()>,
141}
142
143impl<'a> SubplotToken<'a> {
144 pub fn end(self) {
146 }
148}
149
150impl<'a> Drop for SubplotToken<'a> {
151 fn drop(&mut self) {
152 unsafe {
153 sys::ImPlot_EndSubplots();
154 }
155 }
156}
157
158pub struct MultiAxisPlot<'a> {
160 title: &'a str,
161 size: Option<[f32; 2]>,
162 y_axes: Vec<YAxisConfig<'a>>,
163}
164
165pub struct YAxisConfig<'a> {
167 pub label: Option<&'a str>,
168 pub flags: AxisFlags,
169 pub range: Option<(f64, f64)>,
170}
171
172impl<'a> MultiAxisPlot<'a> {
173 pub fn new(title: &'a str) -> Self {
175 Self {
176 title,
177 size: None,
178 y_axes: Vec::new(),
179 }
180 }
181
182 pub fn with_size(mut self, size: [f32; 2]) -> Self {
184 self.size = Some(size);
185 self
186 }
187
188 pub fn add_y_axis(mut self, config: YAxisConfig<'a>) -> Self {
190 self.y_axes.push(config);
191 self
192 }
193
194 pub fn begin(self) -> Result<MultiAxisToken<'a>, PlotError> {
196 let title_cstr =
197 CString::new(self.title).map_err(|e| PlotError::StringConversion(e.to_string()))?;
198
199 for axis in &self.y_axes {
200 if let Some(label) = axis.label
201 && label.contains('\0')
202 {
203 return Err(PlotError::StringConversion(
204 "Axis label contained an interior NUL byte".to_string(),
205 ));
206 }
207 }
208
209 let size = self.size.unwrap_or([-1.0, -1.0]);
210 let size_vec = sys::ImVec2_c {
211 x: size[0],
212 y: size[1],
213 };
214
215 let success = unsafe { sys::ImPlot_BeginPlot(title_cstr.as_ptr(), size_vec, 0) };
216
217 if success {
218 let mut axis_labels: Vec<CString> = Vec::new();
219
220 for (i, axis_config) in self.y_axes.iter().enumerate() {
222 let label_ptr = if let Some(label) = axis_config.label {
223 let cstr = CString::new(label)
224 .map_err(|e| PlotError::StringConversion(e.to_string()))?;
225 let ptr = cstr.as_ptr();
226 axis_labels.push(cstr);
227 ptr
228 } else {
229 std::ptr::null()
230 };
231
232 unsafe {
233 let axis_enum = (i as i32) + 3; sys::ImPlot_SetupAxis(axis_enum, label_ptr, axis_config.flags.bits() as i32);
235
236 if let Some((min, max)) = axis_config.range {
237 sys::ImPlot_SetupAxisLimits(axis_enum, min, max, 0);
238 }
239 }
240 }
241
242 Ok(MultiAxisToken {
243 _title: title_cstr,
244 _axis_labels: axis_labels,
245 _scope: PlotScopeGuard::new(),
246 _phantom: PhantomData,
247 })
248 } else {
249 Err(PlotError::PlotCreationFailed(
250 "Failed to begin multi-axis plot".to_string(),
251 ))
252 }
253 }
254}
255
256pub struct MultiAxisToken<'a> {
258 _title: CString,
259 _axis_labels: Vec<CString>,
260 _scope: PlotScopeGuard,
261 _phantom: PhantomData<&'a ()>,
262}
263
264impl<'a> MultiAxisToken<'a> {
265 pub fn set_y_axis(&self, axis: i32) {
267 unsafe {
268 sys::ImPlot_SetAxes(
269 0, axis + 3, );
272 }
273 }
274
275 pub fn end(self) {
277 }
279}
280
281impl<'a> Drop for MultiAxisToken<'a> {
282 fn drop(&mut self) {
283 unsafe {
284 sys::ImPlot_EndPlot();
285 }
286 }
287}
288
289pub struct LegendManager;
291
292impl LegendManager {
293 pub fn setup(location: LegendLocation, flags: LegendFlags) {
295 unsafe {
296 sys::ImPlot_SetupLegend(location as i32, flags.bits() as i32);
297 }
298 }
299
300 pub fn begin_custom(label: &str, _size: [f32; 2]) -> Result<LegendToken, PlotError> {
302 let label_cstr =
303 CString::new(label).map_err(|e| PlotError::StringConversion(e.to_string()))?;
304
305 let success = unsafe {
306 sys::ImPlot_BeginLegendPopup(
307 label_cstr.as_ptr(),
308 1, )
310 };
311
312 if success {
313 Ok(LegendToken { _label: label_cstr })
314 } else {
315 Err(PlotError::PlotCreationFailed(
316 "Failed to begin legend".to_string(),
317 ))
318 }
319 }
320}
321
322#[repr(i32)]
324pub enum LegendLocation {
325 Center = sys::ImPlotLocation_Center as i32,
326 North = sys::ImPlotLocation_North as i32,
327 South = sys::ImPlotLocation_South as i32,
328 West = sys::ImPlotLocation_West as i32,
329 East = sys::ImPlotLocation_East as i32,
330 NorthWest = sys::ImPlotLocation_NorthWest as i32,
331 NorthEast = sys::ImPlotLocation_NorthEast as i32,
332 SouthWest = sys::ImPlotLocation_SouthWest as i32,
333 SouthEast = sys::ImPlotLocation_SouthEast as i32,
334}
335
336bitflags::bitflags! {
337 pub struct LegendFlags: u32 {
339 const NONE = sys::ImPlotLegendFlags_None as u32;
340 const NO_BUTTONS = sys::ImPlotLegendFlags_NoButtons as u32;
341 const NO_HIGHLIGHT_ITEM = sys::ImPlotLegendFlags_NoHighlightItem as u32;
342 const NO_HIGHLIGHT_AXIS = sys::ImPlotLegendFlags_NoHighlightAxis as u32;
343 const NO_MENUS = sys::ImPlotLegendFlags_NoMenus as u32;
344 const OUTSIDE = sys::ImPlotLegendFlags_Outside as u32;
345 const HORIZONTAL = sys::ImPlotLegendFlags_Horizontal as u32;
346 const SORT = sys::ImPlotLegendFlags_Sort as u32;
347 }
349}
350
351pub struct LegendToken {
353 _label: CString,
354}
355
356impl LegendToken {
357 pub fn end(self) {
359 }
361}
362
363impl Drop for LegendToken {
364 fn drop(&mut self) {
365 unsafe {
366 sys::ImPlot_EndLegendPopup();
367 }
368 }
369}