use crate::geom::{Point, ScreenPoint, ScreenRect};
use crate::series::SeriesId;
use crate::transform::Transform;
use crate::view::{Range, Viewport};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum HitRegion {
Plot,
XAxis,
YAxis,
Outside,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) struct PlotRegions {
pub(crate) plot: ScreenRect,
pub(crate) x_axis: ScreenRect,
pub(crate) y_axis: ScreenRect,
}
impl PlotRegions {
pub(crate) fn hit_test(&self, point: ScreenPoint) -> HitRegion {
if contains(self.plot, point) {
HitRegion::Plot
} else if contains(self.x_axis, point) {
HitRegion::XAxis
} else if contains(self.y_axis, point) {
HitRegion::YAxis
} else {
HitRegion::Outside
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Pin {
pub series_id: SeriesId,
pub point_index: usize,
}
pub(crate) fn toggle_pin(pins: &mut Vec<Pin>, pin: Pin) -> bool {
if let Some(index) = pins.iter().position(|existing| *existing == pin) {
pins.swap_remove(index);
false
} else {
pins.push(pin);
true
}
}
pub(crate) fn pan_viewport(
viewport: Viewport,
delta_pixels: ScreenPoint,
transform: &Transform,
) -> Option<Viewport> {
let origin = transform.screen_to_data(ScreenPoint::new(0.0, 0.0))?;
let shifted = transform.screen_to_data(ScreenPoint::new(delta_pixels.x, delta_pixels.y))?;
let dx = shifted.x - origin.x;
let dy = shifted.y - origin.y;
Some(Viewport::new(
Range::new(viewport.x.min - dx, viewport.x.max - dx),
Range::new(viewport.y.min - dy, viewport.y.max - dy),
))
}
pub(crate) fn zoom_viewport(
viewport: Viewport,
center: Point,
factor_x: f64,
factor_y: f64,
) -> Viewport {
let x_min = center.x + (viewport.x.min - center.x) * factor_x;
let x_max = center.x + (viewport.x.max - center.x) * factor_x;
let y_min = center.y + (viewport.y.min - center.y) * factor_y;
let y_max = center.y + (viewport.y.max - center.y) * factor_y;
Viewport::new(Range::new(x_min, x_max), Range::new(y_min, y_max))
}
pub(crate) fn zoom_to_rect(
viewport: Viewport,
rect: ScreenRect,
transform: &Transform,
) -> Option<Viewport> {
if rect.width().abs() < 2.0 || rect.height().abs() < 2.0 {
return Some(viewport);
}
let data_min = transform.screen_to_data(rect.min)?;
let data_max = transform.screen_to_data(rect.max)?;
Some(Viewport::new(
Range::new(data_min.x, data_max.x),
Range::new(data_min.y, data_max.y),
))
}
pub(crate) fn zoom_factor_from_drag(delta_pixels: f32, axis_pixels: f32) -> f64 {
if axis_pixels <= 0.0 {
return 1.0;
}
let normalized = delta_pixels as f64 / axis_pixels as f64;
(1.0 - normalized).clamp(0.1, 10.0)
}
fn contains(rect: ScreenRect, point: ScreenPoint) -> bool {
point.x >= rect.min.x && point.x <= rect.max.x && point.y >= rect.min.y && point.y <= rect.max.y
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hit_test_regions() {
let regions = PlotRegions {
plot: ScreenRect::new(ScreenPoint::new(0.0, 0.0), ScreenPoint::new(10.0, 10.0)),
x_axis: ScreenRect::new(ScreenPoint::new(0.0, 10.0), ScreenPoint::new(10.0, 12.0)),
y_axis: ScreenRect::new(ScreenPoint::new(-2.0, 0.0), ScreenPoint::new(0.0, 10.0)),
};
assert_eq!(
regions.hit_test(ScreenPoint::new(5.0, 5.0)),
HitRegion::Plot
);
assert_eq!(
regions.hit_test(ScreenPoint::new(5.0, 11.0)),
HitRegion::XAxis
);
assert_eq!(
regions.hit_test(ScreenPoint::new(-1.0, 5.0)),
HitRegion::YAxis
);
}
}