Skip to main content

dear_implot/context/
axis.rs

1use super::ui::PlotUi;
2use super::validation::{
3    assert_axis_constraint_range, assert_axis_limit_range, assert_axis_zoom_range,
4    assert_finite_f64_slice, axis_tick_count_to_i32,
5};
6use crate::{Axis, AxisFlags, AxisFormat, PlotCond, XAxis, YAxis, sys};
7use dear_imgui_rs::{with_scratch_txt, with_scratch_txt_slice, with_scratch_txt_two};
8use std::os::raw::c_char;
9
10impl<'ui> PlotUi<'ui> {
11    /// Setup a specific X axis
12    pub fn setup_x_axis(&self, axis: XAxis, label: Option<&str>, flags: AxisFlags) {
13        self.with_bound_context(|| {
14            let label = label.filter(|s| !s.contains('\0'));
15            match label {
16                Some(label) => with_scratch_txt(label, |ptr| unsafe {
17                    sys::ImPlot_SetupAxis(
18                        axis as sys::ImAxis,
19                        ptr,
20                        flags.bits() as sys::ImPlotAxisFlags,
21                    )
22                }),
23                None => unsafe {
24                    sys::ImPlot_SetupAxis(
25                        axis as sys::ImAxis,
26                        std::ptr::null(),
27                        flags.bits() as sys::ImPlotAxisFlags,
28                    )
29                },
30            }
31        })
32    }
33
34    /// Setup a specific Y axis
35    pub fn setup_y_axis(&self, axis: YAxis, label: Option<&str>, flags: AxisFlags) {
36        self.with_bound_context(|| {
37            let label = label.filter(|s| !s.contains('\0'));
38            match label {
39                Some(label) => with_scratch_txt(label, |ptr| unsafe {
40                    sys::ImPlot_SetupAxis(
41                        axis as sys::ImAxis,
42                        ptr,
43                        flags.bits() as sys::ImPlotAxisFlags,
44                    )
45                }),
46                None => unsafe {
47                    sys::ImPlot_SetupAxis(
48                        axis as sys::ImAxis,
49                        std::ptr::null(),
50                        flags.bits() as sys::ImPlotAxisFlags,
51                    )
52                },
53            }
54        })
55    }
56
57    /// Setup axis limits for a specific X axis
58    pub fn setup_x_axis_limits(&self, axis: XAxis, min: f64, max: f64, cond: PlotCond) {
59        assert_axis_limit_range("PlotUi::setup_x_axis_limits()", min, max);
60        self.with_bound_context(|| unsafe {
61            sys::ImPlot_SetupAxisLimits(axis as sys::ImAxis, min, max, cond as sys::ImPlotCond)
62        })
63    }
64
65    /// Setup axis limits for a specific Y axis
66    pub fn setup_y_axis_limits(&self, axis: YAxis, min: f64, max: f64, cond: PlotCond) {
67        assert_axis_limit_range("PlotUi::setup_y_axis_limits()", min, max);
68        self.with_bound_context(|| unsafe {
69            sys::ImPlot_SetupAxisLimits(axis as sys::ImAxis, min, max, cond as sys::ImPlotCond)
70        })
71    }
72
73    /// Link an enabled axis to external min/max values for the active plot.
74    ///
75    /// # Safety
76    ///
77    /// Call after `begin_plot` succeeds, before plotting or any other setup-locking call.
78    /// The axis must be enabled. Every supplied value must stay initialized at a stable
79    /// address until this `PlotContext` is destroyed, unless its link is explicitly cleared.
80    /// To clear it, begin the same plot ID, enable this axis, call this method with `None`
81    /// for the corresponding value, and end that plot. Also clear any pending next-plot
82    /// link for that value. `EndPlot` alone does not unlink every axis: later uses of the
83    /// same plot can still write through retained pointers, even when an axis is disabled.
84    /// Do not move, drop, or otherwise access values while linked, including during
85    /// unwinding. Separate linked values must not alias.
86    ///
87    /// ```compile_fail
88    /// use dear_implot::{Axis, PlotUi};
89    /// fn link(plot_ui: &PlotUi<'_>, min: &mut f64, max: &mut f64) {
90    ///     plot_ui.setup_axis_links(Axis::Y1, Some(min), Some(max));
91    /// }
92    /// ```
93    pub unsafe fn setup_axis_links(
94        &self,
95        axis: Axis,
96        link_min: Option<&mut f64>,
97        link_max: Option<&mut f64>,
98    ) {
99        let pmin = link_min.map_or(std::ptr::null_mut(), |r| r as *mut f64);
100        let pmax = link_max.map_or(std::ptr::null_mut(), |r| r as *mut f64);
101        self.with_bound_context(|| unsafe { sys::ImPlot_SetupAxisLinks(axis.to_sys(), pmin, pmax) })
102    }
103
104    /// Link a raw axis to external min/max values (live binding).
105    ///
106    /// # Safety
107    ///
108    /// `axis` must be a valid ImPlot `ImAxis` value for the active plot. Passing an
109    /// out-of-range value lets ImPlot index internal axis arrays out of bounds.
110    /// Call after `begin_plot` succeeds, before any setup-locking call, with the axis enabled.
111    /// Every supplied value must stay initialized at a stable address until the owning
112    /// `PlotContext` is destroyed or the corresponding stored and pending links are
113    /// explicitly cleared. Clear the stored link on the same plot ID and enabled axis
114    /// with `None`, then end that plot; clear pending next-plot links separately.
115    /// `EndPlot` alone does not unlink every axis, and later plots may still write to it.
116    /// Do not move, drop, or otherwise access values while linked, including during
117    /// unwinding. Separate linked values must not alias.
118    pub unsafe fn setup_axis_links_unchecked(
119        &self,
120        axis: sys::ImAxis,
121        link_min: Option<&mut f64>,
122        link_max: Option<&mut f64>,
123    ) {
124        let pmin = link_min.map_or(std::ptr::null_mut(), |r| r as *mut f64);
125        let pmax = link_max.map_or(std::ptr::null_mut(), |r| r as *mut f64);
126        self.with_bound_context(|| unsafe { sys::ImPlot_SetupAxisLinks(axis, pmin, pmax) })
127    }
128
129    /// Setup both axes labels/flags at once
130    pub fn setup_axes(
131        &self,
132        x_label: Option<&str>,
133        y_label: Option<&str>,
134        x_flags: AxisFlags,
135        y_flags: AxisFlags,
136    ) {
137        self.with_bound_context(|| {
138            let x_label = x_label.filter(|s| !s.contains('\0'));
139            let y_label = y_label.filter(|s| !s.contains('\0'));
140
141            match (x_label, y_label) {
142                (Some(x_label), Some(y_label)) => {
143                    with_scratch_txt_two(x_label, y_label, |xp, yp| unsafe {
144                        sys::ImPlot_SetupAxes(
145                            xp,
146                            yp,
147                            x_flags.bits() as sys::ImPlotAxisFlags,
148                            y_flags.bits() as sys::ImPlotAxisFlags,
149                        )
150                    })
151                }
152                (Some(x_label), None) => with_scratch_txt(x_label, |xp| unsafe {
153                    sys::ImPlot_SetupAxes(
154                        xp,
155                        std::ptr::null(),
156                        x_flags.bits() as sys::ImPlotAxisFlags,
157                        y_flags.bits() as sys::ImPlotAxisFlags,
158                    )
159                }),
160                (None, Some(y_label)) => with_scratch_txt(y_label, |yp| unsafe {
161                    sys::ImPlot_SetupAxes(
162                        std::ptr::null(),
163                        yp,
164                        x_flags.bits() as sys::ImPlotAxisFlags,
165                        y_flags.bits() as sys::ImPlotAxisFlags,
166                    )
167                }),
168                (None, None) => unsafe {
169                    sys::ImPlot_SetupAxes(
170                        std::ptr::null(),
171                        std::ptr::null(),
172                        x_flags.bits() as sys::ImPlotAxisFlags,
173                        y_flags.bits() as sys::ImPlotAxisFlags,
174                    )
175                },
176            }
177        })
178    }
179
180    /// Setup axes limits (both) at once
181    pub fn setup_axes_limits(
182        &self,
183        x_min: f64,
184        x_max: f64,
185        y_min: f64,
186        y_max: f64,
187        cond: PlotCond,
188    ) {
189        assert_axis_limit_range("PlotUi::setup_axes_limits() x axis", x_min, x_max);
190        assert_axis_limit_range("PlotUi::setup_axes_limits() y axis", y_min, y_max);
191        self.with_bound_context(|| unsafe {
192            sys::ImPlot_SetupAxesLimits(x_min, x_max, y_min, y_max, cond as sys::ImPlotCond)
193        })
194    }
195
196    /// Call after axis setup to finalize configuration
197    pub fn setup_finish(&self) {
198        self.with_bound_context(|| unsafe { sys::ImPlot_SetupFinish() })
199    }
200
201    /// Set next frame limits for a specific axis
202    pub fn set_next_x_axis_limits(&self, axis: XAxis, min: f64, max: f64, cond: PlotCond) {
203        assert_axis_limit_range("PlotUi::set_next_x_axis_limits()", min, max);
204        self.with_bound_context(|| unsafe {
205            sys::ImPlot_SetNextAxisLimits(axis as sys::ImAxis, min, max, cond as sys::ImPlotCond)
206        })
207    }
208
209    /// Set next frame limits for a specific axis
210    pub fn set_next_y_axis_limits(&self, axis: YAxis, min: f64, max: f64, cond: PlotCond) {
211        assert_axis_limit_range("PlotUi::set_next_y_axis_limits()", min, max);
212        self.with_bound_context(|| unsafe {
213            sys::ImPlot_SetNextAxisLimits(axis as sys::ImAxis, min, max, cond as sys::ImPlotCond)
214        })
215    }
216
217    /// Link an axis to external min/max values for the next plot.
218    ///
219    /// # Safety
220    ///
221    /// Call before `begin_plot`, with no plot active. Enable the axis in the next plot.
222    /// Every supplied value must stay initialized at a stable address until the owning
223    /// `PlotContext` is destroyed or all corresponding pending and stored links are cleared.
224    /// Clear a pending link with this method and `None`; clear an applied link with
225    /// `setup_axis_links` and `None` on the same plot ID and enabled axis, then end that plot.
226    /// `EndPlot`, a rejected `begin_plot`, a frame change, or disabling an axis does not
227    /// generally unlink it. Do not move, drop, or otherwise access values while linked,
228    /// including during unwinding. Separate linked values must not alias.
229    ///
230    /// ```compile_fail
231    /// use dear_implot::{Axis, PlotUi};
232    /// fn link(plot_ui: &PlotUi<'_>, min: &mut f64, max: &mut f64) {
233    ///     plot_ui.set_next_axis_links(Axis::Y1, Some(min), Some(max));
234    /// }
235    /// ```
236    pub unsafe fn set_next_axis_links(
237        &self,
238        axis: Axis,
239        link_min: Option<&mut f64>,
240        link_max: Option<&mut f64>,
241    ) {
242        let pmin = link_min.map_or(std::ptr::null_mut(), |r| r as *mut f64);
243        let pmax = link_max.map_or(std::ptr::null_mut(), |r| r as *mut f64);
244        self.with_bound_context(|| unsafe {
245            sys::ImPlot_SetNextAxisLinks(axis.to_sys(), pmin, pmax)
246        })
247    }
248
249    /// Link a raw axis to external min/max values for the next plot.
250    ///
251    /// # Safety
252    ///
253    /// `axis` must be a valid ImPlot `ImAxis` value. Passing an out-of-range
254    /// value lets ImPlot index internal next-plot arrays out of bounds.
255    /// Call before `begin_plot`, with no plot active, and enable the axis in the next plot.
256    /// Every supplied value must stay initialized at a stable address until the owning
257    /// `PlotContext` is destroyed or all corresponding pending and stored links are cleared.
258    /// Clear pending links with `None` here; clear applied links with `None` on the same
259    /// plot ID and enabled axis using `setup_axis_links_unchecked`, then end that plot.
260    /// `EndPlot`, rejected `begin_plot` calls, frame changes, and disabled axes do not
261    /// generally unlink it. Do not move, drop, or otherwise access values while linked,
262    /// including during unwinding. Separate linked values must not alias.
263    pub unsafe fn set_next_axis_links_unchecked(
264        &self,
265        axis: sys::ImAxis,
266        link_min: Option<&mut f64>,
267        link_max: Option<&mut f64>,
268    ) {
269        let pmin = link_min.map_or(std::ptr::null_mut(), |r| r as *mut f64);
270        let pmax = link_max.map_or(std::ptr::null_mut(), |r| r as *mut f64);
271        self.with_bound_context(|| unsafe { sys::ImPlot_SetNextAxisLinks(axis, pmin, pmax) })
272    }
273
274    /// Set limits for both axes on the next plot
275    pub fn set_next_axes_limits(
276        &self,
277        x_min: f64,
278        x_max: f64,
279        y_min: f64,
280        y_max: f64,
281        cond: PlotCond,
282    ) {
283        assert_axis_limit_range("PlotUi::set_next_axes_limits() x axis", x_min, x_max);
284        assert_axis_limit_range("PlotUi::set_next_axes_limits() y axis", y_min, y_max);
285        self.with_bound_context(|| unsafe {
286            sys::ImPlot_SetNextAxesLimits(x_min, x_max, y_min, y_max, cond as sys::ImPlotCond)
287        })
288    }
289
290    /// Fit both axes on the next plot
291    pub fn set_next_axes_to_fit(&self) {
292        self.with_bound_context(|| unsafe { sys::ImPlot_SetNextAxesToFit() })
293    }
294
295    /// Fit a specific axis on the next plot
296    pub fn set_next_axis_to_fit(&self, axis: Axis) {
297        self.with_bound_context(|| unsafe { sys::ImPlot_SetNextAxisToFit(axis.to_sys()) })
298    }
299
300    /// Fit a raw axis on the next plot.
301    ///
302    /// # Safety
303    ///
304    /// `axis` must be a valid ImPlot `ImAxis` value. Passing an out-of-range
305    /// value lets ImPlot index internal next-plot arrays out of bounds.
306    pub unsafe fn set_next_axis_to_fit_unchecked(&self, axis: sys::ImAxis) {
307        self.with_bound_context(|| unsafe { sys::ImPlot_SetNextAxisToFit(axis) })
308    }
309
310    /// Fit a specific X axis on the next plot
311    pub fn set_next_x_axis_to_fit(&self, axis: XAxis) {
312        self.with_bound_context(|| unsafe { sys::ImPlot_SetNextAxisToFit(axis as sys::ImAxis) })
313    }
314
315    /// Fit a specific Y axis on the next plot
316    pub fn set_next_y_axis_to_fit(&self, axis: YAxis) {
317        self.with_bound_context(|| unsafe { sys::ImPlot_SetNextAxisToFit(axis as sys::ImAxis) })
318    }
319
320    /// Setup ticks with explicit positions and optional labels for an X axis.
321    ///
322    /// If `labels` is provided, it must have the same length as `values`.
323    pub fn setup_x_axis_ticks_positions(
324        &self,
325        axis: XAxis,
326        values: &[f64],
327        labels: Option<&[&str]>,
328        keep_default: bool,
329    ) {
330        assert_finite_f64_slice("PlotUi::setup_x_axis_ticks_positions()", "values", values);
331        self.with_bound_context(|| {
332            let count = match i32::try_from(values.len()) {
333                Ok(v) => v,
334                Err(_) => return,
335            };
336            if let Some(labels) = labels {
337                if labels.len() != values.len() {
338                    return;
339                }
340                let cleaned: Vec<&str> = labels
341                    .iter()
342                    .map(|&s| if s.contains('\0') { "" } else { s })
343                    .collect();
344                with_scratch_txt_slice(&cleaned, |ptrs| unsafe {
345                    sys::ImPlot_SetupAxisTicks_doublePtr(
346                        axis as sys::ImAxis,
347                        values.as_ptr(),
348                        count,
349                        ptrs.as_ptr() as *const *const c_char,
350                        keep_default,
351                    )
352                })
353            } else {
354                unsafe {
355                    sys::ImPlot_SetupAxisTicks_doublePtr(
356                        axis as sys::ImAxis,
357                        values.as_ptr(),
358                        count,
359                        std::ptr::null(),
360                        keep_default,
361                    )
362                }
363            }
364        })
365    }
366
367    /// Setup ticks with explicit positions and optional labels for a Y axis.
368    ///
369    /// If `labels` is provided, it must have the same length as `values`.
370    pub fn setup_y_axis_ticks_positions(
371        &self,
372        axis: YAxis,
373        values: &[f64],
374        labels: Option<&[&str]>,
375        keep_default: bool,
376    ) {
377        assert_finite_f64_slice("PlotUi::setup_y_axis_ticks_positions()", "values", values);
378        self.with_bound_context(|| {
379            let count = match i32::try_from(values.len()) {
380                Ok(v) => v,
381                Err(_) => return,
382            };
383            if let Some(labels) = labels {
384                if labels.len() != values.len() {
385                    return;
386                }
387                let cleaned: Vec<&str> = labels
388                    .iter()
389                    .map(|&s| if s.contains('\0') { "" } else { s })
390                    .collect();
391                with_scratch_txt_slice(&cleaned, |ptrs| unsafe {
392                    sys::ImPlot_SetupAxisTicks_doublePtr(
393                        axis as sys::ImAxis,
394                        values.as_ptr(),
395                        count,
396                        ptrs.as_ptr() as *const *const c_char,
397                        keep_default,
398                    )
399                })
400            } else {
401                unsafe {
402                    sys::ImPlot_SetupAxisTicks_doublePtr(
403                        axis as sys::ImAxis,
404                        values.as_ptr(),
405                        count,
406                        std::ptr::null(),
407                        keep_default,
408                    )
409                }
410            }
411        })
412    }
413
414    /// Setup ticks on a range with tick count and optional labels for an X axis.
415    ///
416    /// If `labels` is provided, it must have length `n_ticks`.
417    pub fn setup_x_axis_ticks_range(
418        &self,
419        axis: XAxis,
420        v_min: f64,
421        v_max: f64,
422        n_ticks: usize,
423        labels: Option<&[&str]>,
424        keep_default: bool,
425    ) {
426        assert_axis_limit_range("PlotUi::setup_x_axis_ticks_range()", v_min, v_max);
427        let n_ticks_i32 = axis_tick_count_to_i32("PlotUi::setup_x_axis_ticks_range()", n_ticks);
428        self.with_bound_context(|| {
429            if let Some(labels) = labels {
430                if labels.len() != n_ticks {
431                    return;
432                }
433                let cleaned: Vec<&str> = labels
434                    .iter()
435                    .map(|&s| if s.contains('\0') { "" } else { s })
436                    .collect();
437                with_scratch_txt_slice(&cleaned, |ptrs| unsafe {
438                    sys::ImPlot_SetupAxisTicks_double(
439                        axis as sys::ImAxis,
440                        v_min,
441                        v_max,
442                        n_ticks_i32,
443                        ptrs.as_ptr() as *const *const c_char,
444                        keep_default,
445                    )
446                })
447            } else {
448                unsafe {
449                    sys::ImPlot_SetupAxisTicks_double(
450                        axis as sys::ImAxis,
451                        v_min,
452                        v_max,
453                        n_ticks_i32,
454                        std::ptr::null(),
455                        keep_default,
456                    )
457                }
458            }
459        })
460    }
461
462    /// Setup ticks on a range with tick count and optional labels for a Y axis.
463    ///
464    /// If `labels` is provided, it must have length `n_ticks`.
465    pub fn setup_y_axis_ticks_range(
466        &self,
467        axis: YAxis,
468        v_min: f64,
469        v_max: f64,
470        n_ticks: usize,
471        labels: Option<&[&str]>,
472        keep_default: bool,
473    ) {
474        assert_axis_limit_range("PlotUi::setup_y_axis_ticks_range()", v_min, v_max);
475        let n_ticks_i32 = axis_tick_count_to_i32("PlotUi::setup_y_axis_ticks_range()", n_ticks);
476        self.with_bound_context(|| {
477            if let Some(labels) = labels {
478                if labels.len() != n_ticks {
479                    return;
480                }
481                let cleaned: Vec<&str> = labels
482                    .iter()
483                    .map(|&s| if s.contains('\0') { "" } else { s })
484                    .collect();
485                with_scratch_txt_slice(&cleaned, |ptrs| unsafe {
486                    sys::ImPlot_SetupAxisTicks_double(
487                        axis as sys::ImAxis,
488                        v_min,
489                        v_max,
490                        n_ticks_i32,
491                        ptrs.as_ptr() as *const *const c_char,
492                        keep_default,
493                    )
494                })
495            } else {
496                unsafe {
497                    sys::ImPlot_SetupAxisTicks_double(
498                        axis as sys::ImAxis,
499                        v_min,
500                        v_max,
501                        n_ticks_i32,
502                        std::ptr::null(),
503                        keep_default,
504                    )
505                }
506            }
507        })
508    }
509
510    /// Setup a validated tick label format for a specific X axis.
511    pub fn setup_x_axis_format(&self, axis: XAxis, format: &AxisFormat<'_>) {
512        self.with_bound_context(|| {
513            with_scratch_txt(format.as_str(), |ptr| unsafe {
514                sys::ImPlot_SetupAxisFormat_Str(axis as sys::ImAxis, ptr)
515            })
516        })
517    }
518
519    /// Setup a validated tick label format for a specific Y axis.
520    pub fn setup_y_axis_format(&self, axis: YAxis, format: &AxisFormat<'_>) {
521        self.with_bound_context(|| {
522            with_scratch_txt(format.as_str(), |ptr| unsafe {
523                sys::ImPlot_SetupAxisFormat_Str(axis as sys::ImAxis, ptr)
524            })
525        })
526    }
527
528    /// Setup scale for a specific X axis (pass sys::ImPlotScale variant)
529    pub fn setup_x_axis_scale(&self, axis: XAxis, scale: sys::ImPlotScale) {
530        self.with_bound_context(|| unsafe {
531            sys::ImPlot_SetupAxisScale_PlotScale(axis as sys::ImAxis, scale)
532        })
533    }
534
535    /// Setup scale for a specific Y axis (pass sys::ImPlotScale variant)
536    pub fn setup_y_axis_scale(&self, axis: YAxis, scale: sys::ImPlotScale) {
537        self.with_bound_context(|| unsafe {
538            sys::ImPlot_SetupAxisScale_PlotScale(axis as sys::ImAxis, scale)
539        })
540    }
541
542    /// Setup axis limits constraints
543    pub fn setup_axis_limits_constraints(&self, axis: Axis, v_min: f64, v_max: f64) {
544        assert_axis_constraint_range("PlotUi::setup_axis_limits_constraints()", v_min, v_max);
545        self.with_bound_context(|| unsafe {
546            sys::ImPlot_SetupAxisLimitsConstraints(axis.to_sys(), v_min, v_max)
547        })
548    }
549
550    /// Setup raw axis limits constraints.
551    ///
552    /// # Safety
553    ///
554    /// `axis` must be a valid ImPlot `ImAxis` value for the active plot. Passing an
555    /// out-of-range value lets ImPlot index internal axis arrays out of bounds.
556    pub unsafe fn setup_axis_limits_constraints_unchecked(
557        &self,
558        axis: sys::ImAxis,
559        v_min: f64,
560        v_max: f64,
561    ) {
562        assert_axis_constraint_range(
563            "PlotUi::setup_axis_limits_constraints_unchecked()",
564            v_min,
565            v_max,
566        );
567        self.with_bound_context(|| unsafe {
568            sys::ImPlot_SetupAxisLimitsConstraints(axis, v_min, v_max)
569        })
570    }
571
572    /// Setup axis zoom constraints
573    pub fn setup_axis_zoom_constraints(&self, axis: Axis, z_min: f64, z_max: f64) {
574        assert_axis_zoom_range("PlotUi::setup_axis_zoom_constraints()", z_min, z_max);
575        self.with_bound_context(|| unsafe {
576            sys::ImPlot_SetupAxisZoomConstraints(axis.to_sys(), z_min, z_max)
577        })
578    }
579
580    /// Setup raw axis zoom constraints.
581    ///
582    /// # Safety
583    ///
584    /// `axis` must be a valid ImPlot `ImAxis` value for the active plot. Passing an
585    /// out-of-range value lets ImPlot index internal axis arrays out of bounds.
586    pub unsafe fn setup_axis_zoom_constraints_unchecked(
587        &self,
588        axis: sys::ImAxis,
589        z_min: f64,
590        z_max: f64,
591    ) {
592        assert_axis_zoom_range(
593            "PlotUi::setup_axis_zoom_constraints_unchecked()",
594            z_min,
595            z_max,
596        );
597        self.with_bound_context(|| unsafe {
598            sys::ImPlot_SetupAxisZoomConstraints(axis, z_min, z_max)
599        })
600    }
601}