1use crate::{AxisFlags, plots::PlotError, sys};
7use std::ffi::CString;
8use std::marker::PhantomData;
9
10pub 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 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 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 pub fn with_size(mut self, size: [f32; 2]) -> Self {
53 self.size = Some(size);
54 self
55 }
56
57 pub fn with_flags(mut self, flags: SubplotFlags) -> Self {
59 self.flags = flags;
60 self
61 }
62
63 pub fn with_row_ratios(mut self, ratios: &'a [f32]) -> Self {
65 self.row_ratios = Some(ratios);
66 self
67 }
68
69 pub fn with_col_ratios(mut self, ratios: &'a [f32]) -> Self {
71 self.col_ratios = Some(ratios);
72 self
73 }
74
75 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
121pub struct SubplotToken<'a> {
123 _title: CString,
124 _phantom: PhantomData<&'a ()>,
125}
126
127impl<'a> SubplotToken<'a> {
128 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
144pub struct MultiAxisPlot<'a> {
146 title: &'a str,
147 size: Option<[f32; 2]>,
148 y_axes: Vec<YAxisConfig<'a>>,
149}
150
151pub 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 pub fn new(title: &'a str) -> Self {
161 Self {
162 title,
163 size: None,
164 y_axes: Vec::new(),
165 }
166 }
167
168 pub fn with_size(mut self, size: [f32; 2]) -> Self {
170 self.size = Some(size);
171 self
172 }
173
174 pub fn add_y_axis(mut self, config: YAxisConfig<'a>) -> Self {
176 self.y_axes.push(config);
177 self
178 }
179
180 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 for (i, axis_config) in self.y_axes.iter().enumerate() {
196 if i > 0 {
197 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; 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
245pub struct MultiAxisToken<'a> {
247 _title: CString,
248 _axis_labels: Vec<CString>,
249 _phantom: PhantomData<&'a ()>,
250}
251
252impl<'a> MultiAxisToken<'a> {
253 pub fn set_y_axis(&self, axis: i32) {
255 unsafe {
256 sys::ImPlot_SetAxes(
257 0, axis + 3, );
260 }
261 }
262
263 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
279pub struct LegendManager;
281
282impl LegendManager {
283 pub fn setup(location: LegendLocation, flags: LegendFlags) {
285 unsafe {
286 sys::ImPlot_SetupLegend(location as i32, flags.bits() as i32);
287 }
288 }
289
290 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, )
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#[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 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
340pub struct LegendToken {
342 _label: CString,
343}
344
345impl LegendToken {
346 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}