use crate::widget::high_level::{ImageView, PlotWidget, ScatterView};
pub fn next_curve_style_state(current: (bool, bool)) -> (bool, bool) {
const STATES: [(bool, bool); 3] = [(true, false), (true, true), (false, true)];
if current == (false, false) {
return (true, false);
}
match STATES.iter().position(|state| *state == current) {
Some(index) => STATES[(index + 1) % STATES.len()],
None => (true, false),
}
}
pub fn show_axis_toggle(plot: &mut PlotWidget) -> bool {
let next = !plot.plot().axes_displayed();
plot.plot_mut().set_axes_displayed(next);
next
}
pub fn toggle_pan_with_arrow_keys(plot: &mut PlotWidget) -> bool {
let next = !plot.plot().pan_with_arrow_keys();
plot.plot_mut().set_pan_with_arrow_keys(next);
next
}
pub const ZOOM_STEP: f64 = 1.1;
pub fn scale_1d_range(min: f64, max: f64, center: f64, scale: f64, is_log: bool) -> (f64, f64) {
if is_log {
let (lmin, lmax, lcenter) = (min.log10(), max.log10(), center.log10());
if lmin == lmax {
return (min, max);
}
let offset = (lcenter - lmin) / (lmax - lmin);
let range = (lmax - lmin) / scale;
let new_min = lcenter - offset * range;
let new_max = lcenter + (1.0 - offset) * range;
return (10f64.powf(new_min), 10f64.powf(new_max));
}
if min == max {
return (min, max);
}
let offset = (center - min) / (max - min);
let range = (max - min) / scale;
let new_min = center - offset * range;
let new_max = center + (1.0 - offset) * range;
(new_min, new_max)
}
pub fn scale_1d_range_about_midpoint(min: f64, max: f64, scale: f64, is_log: bool) -> (f64, f64) {
let center = if is_log {
(min * max).sqrt()
} else {
0.5 * (min + max)
};
scale_1d_range(min, max, center, scale, is_log)
}
fn apply_zoom(plot: &mut PlotWidget, scale: f64) {
use crate::core::transform::Scale;
let x_log = plot.plot().x_scale == Scale::Log10;
let y_log = plot.plot().y_scale == Scale::Log10;
let (xmin, xmax) = plot.x_limits();
let (nxmin, nxmax) = scale_1d_range_about_midpoint(xmin, xmax, scale, x_log);
let y = plot.y_limits(crate::core::transform::YAxis::Left);
let (nymin, nymax) = match y {
Some((ymin, ymax)) => scale_1d_range_about_midpoint(ymin, ymax, scale, y_log),
None => {
let (_, _, ymin, ymax) = plot.plot().limits;
scale_1d_range_about_midpoint(ymin, ymax, scale, y_log)
}
};
let y2 = plot
.plot()
.y2
.map(|(y2min, y2max)| scale_1d_range_about_midpoint(y2min, y2max, scale, y_log));
plot.set_limits(nxmin, nxmax, nymin, nymax, y2);
}
pub fn zoom_in(plot: &mut PlotWidget) {
apply_zoom(plot, ZOOM_STEP);
}
pub fn zoom_out(plot: &mut PlotWidget) {
apply_zoom(plot, 1.0 / ZOOM_STEP);
}
pub fn zoom_back(plot: &mut PlotWidget) -> bool {
if plot.zoom_back() {
true
} else {
plot.reset_zoom();
false
}
}
pub fn curve_style_cycle(plot: &mut PlotWidget) -> (bool, bool) {
plot.cycle_curve_style()
}
pub fn toggle_x_autoscale(plot: &mut PlotWidget) -> bool {
let next = !plot.plot().x_autoscale();
plot.plot_mut().set_x_autoscale(next);
if next {
plot.reset_zoom();
}
next
}
pub fn toggle_y_autoscale(plot: &mut PlotWidget) -> bool {
let next = !plot.plot().y_autoscale();
plot.plot_mut().set_y_autoscale(next);
if next {
plot.reset_zoom();
}
next
}
pub fn image_colorbar_toggle(view: &mut ImageView) -> bool {
let next = !view.show_colorbar();
view.set_show_colorbar(next);
next
}
pub fn scatter_colorbar_toggle(view: &mut ScatterView) -> bool {
let next = !view.show_colorbar();
view.set_show_colorbar(next);
next
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn curve_style_state_cycles_like_silx_and_recovers_invalid() {
assert_eq!(next_curve_style_state((true, false)), (true, true));
assert_eq!(next_curve_style_state((true, true)), (false, true));
assert_eq!(next_curve_style_state((false, true)), (true, false));
assert_eq!(next_curve_style_state((false, false)), (true, false));
}
#[test]
fn scale_1d_range_zoom_in_and_out_about_midpoint() {
let (zin_min, zin_max) = scale_1d_range_about_midpoint(0.0, 10.0, ZOOM_STEP, false);
assert!((zin_min - (5.0 - 10.0 / 1.1 / 2.0)).abs() < 1e-12);
assert!((zin_max - (5.0 + 10.0 / 1.1 / 2.0)).abs() < 1e-12);
assert!(zin_max - zin_min < 10.0, "zoom in shrinks the range");
let (zout_min, zout_max) = scale_1d_range_about_midpoint(0.0, 10.0, 1.0 / ZOOM_STEP, false);
assert!((zout_min - (-0.5)).abs() < 1e-12);
assert!((zout_max - 10.5).abs() < 1e-12);
assert!(zout_max - zout_min > 10.0, "zoom out grows the range");
}
#[test]
fn scale_1d_range_keeps_off_center_invariant_point() {
let (min, max) = scale_1d_range(2.0, 12.0, 2.0, 2.0, false);
assert!((min - 2.0).abs() < 1e-12, "center stays put");
assert!((max - 7.0).abs() < 1e-12, "range halved from the center");
}
#[test]
fn scale_1d_range_degenerate_is_unchanged() {
assert_eq!(scale_1d_range(3.0, 3.0, 3.0, 1.5, false), (3.0, 3.0));
}
#[test]
fn scale_1d_range_log_scales_in_log10_space() {
let (lo, hi) = scale_1d_range_about_midpoint(1.0, 1000.0, ZOOM_STEP, true);
assert!(
((lo * hi).sqrt() - (1.0_f64 * 1000.0).sqrt()).abs() < 1e-9,
"geometric center fixed"
);
let new_span = hi.log10() - lo.log10();
assert!(
(new_span - 3.0 / ZOOM_STEP).abs() < 1e-9,
"log span / scale"
);
assert!(new_span < 3.0, "zoom in shrinks the log span");
assert!(lo > 0.0 && hi > lo);
}
#[test]
fn zoom_back_restores_pushed_view_then_falls_back_when_empty() {
use crate::core::plot::Plot;
let mut plot = Plot::new(0);
plot.limits = (0.0, 10.0, 0.0, 10.0);
plot.push_limits();
plot.limits = (2.0, 4.0, 2.0, 4.0);
assert_eq!(plot.limits_history_len(), 1);
assert!(plot.zoom_back(), "restored a stored view");
assert_eq!(plot.limits, (0.0, 10.0, 0.0, 10.0));
assert_eq!(plot.limits_history_len(), 0);
assert!(!plot.zoom_back(), "empty history signals fallback");
}
#[test]
fn autoscale_toggle_reset_on_enable_refits_only_that_axis() {
use crate::core::plot::{DataRange, Plot};
let mut plot = Plot::new(0);
plot.limits = (0.0, 1.0, 0.0, 1.0);
plot.set_x_autoscale(false);
plot.set_y_autoscale(true);
let data = DataRange {
x: Some((10.0, 20.0)),
y: Some((-5.0, 5.0)),
y2: None,
};
let next = !plot.x_autoscale();
plot.set_x_autoscale(next);
assert!(next, "X autoscale enabled");
plot.reset_zoom_to_data_range(data);
assert_eq!(plot.limits, (10.0, 20.0, -5.0, 5.0));
}
#[test]
fn autoscale_toggle_disable_does_not_reset() {
use crate::core::plot::Plot;
let mut plot = Plot::new(0);
plot.limits = (3.0, 7.0, 2.0, 8.0);
assert!(plot.x_autoscale(), "default on");
let next = !plot.x_autoscale();
plot.set_x_autoscale(next);
assert!(!next, "X autoscale disabled");
assert_eq!(plot.limits, (3.0, 7.0, 2.0, 8.0));
}
#[test]
fn show_axis_toggle_flips_axes_displayed() {
use crate::core::plot::Plot;
let mut plot = Plot::new(0);
assert!(plot.axes_displayed(), "default is displayed");
let next = !plot.axes_displayed();
plot.set_axes_displayed(next);
assert!(!plot.axes_displayed(), "first toggle hides");
let next = !plot.axes_displayed();
plot.set_axes_displayed(next);
assert!(plot.axes_displayed(), "second toggle shows again");
}
#[test]
fn pan_with_arrow_keys_toggle_flips_flag() {
use crate::core::plot::Plot;
let mut plot = Plot::new(0);
assert!(plot.pan_with_arrow_keys(), "default is enabled (silx True)");
let next = !plot.pan_with_arrow_keys();
plot.set_pan_with_arrow_keys(next);
assert!(!plot.pan_with_arrow_keys(), "first toggle disables");
let next = !plot.pan_with_arrow_keys();
plot.set_pan_with_arrow_keys(next);
assert!(plot.pan_with_arrow_keys(), "second toggle re-enables");
}
}