use super::color::Color;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum PointShape {
Dot,
Plus,
Cross,
Asterisk,
Circle,
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct PlotRect {
pub gutter: usize,
pub top: usize,
pub columns: usize,
pub rows: usize,
}
pub(crate) trait Canvas {
fn set_clip(&mut self, x0: i64, y0: i64, x1: i64, y1: i64);
fn clear_clip(&mut self);
fn dot(&mut self, x: f64, y: f64, color: Color);
fn point(&mut self, x: f64, y: f64, shape: PointShape, color: Color);
fn line(&mut self, from: (f64, f64), to: (f64, f64), color: Color);
fn text(&mut self, column: i64, row: i64, text: &str, color: Color);
fn bar(
&mut self,
span: (f64, f64),
end: f64,
baseline: f64,
positive: bool,
rect: PlotRect,
color: Color,
);
fn marker(&mut self, sx: f64, half_width: f64, sy: f64, color: Color);
fn patch_density(&self) -> (usize, usize);
fn patch(&mut self, column: usize, row: usize, rect: PlotRect, sample: Option<(f64, Color)>);
}
pub(crate) fn trace_line(
from: (f64, f64),
to: (f64, f64),
window: (f64, f64, f64, f64),
mut plot: impl FnMut(i64, i64),
) {
let (wx0, wy0, wx1, wy1) = window;
if wx0 > wx1 || wy0 > wy1 {
return;
}
let Some((from, to)) = clip_segment(from, to, (wx0, wy0), (wx1, wy1)) else {
return;
};
let (x1, y1) = (to.0.round() as i64, to.1.round() as i64);
let (mut x, mut y) = (from.0.round() as i64, from.1.round() as i64);
let dx = (x1 - x).abs();
let dy = -(y1 - y).abs();
let sx = if x < x1 { 1 } else { -1 };
let sy = if y < y1 { 1 } else { -1 };
let mut error = dx + dy;
loop {
plot(x, y);
if x == x1 && y == y1 {
return;
}
let doubled = 2 * error;
if doubled >= dy {
error += dy;
x += sx;
}
if doubled <= dx {
error += dx;
y += sy;
}
}
}
fn clip_segment(
from: (f64, f64),
to: (f64, f64),
min: (f64, f64),
max: (f64, f64),
) -> Option<((f64, f64), (f64, f64))> {
let (dx, dy) = (to.0 - from.0, to.1 - from.1);
let mut enter = 0.0f64;
let mut exit = 1.0f64;
let tests = [
(-dx, from.0 - min.0),
(dx, max.0 - from.0),
(-dy, from.1 - min.1),
(dy, max.1 - from.1),
];
for (p, q) in tests {
if p == 0.0 {
if q < 0.0 {
return None;
}
} else {
let r = q / p;
if p < 0.0 {
if r > exit {
return None;
}
enter = enter.max(r);
} else {
if r < enter {
return None;
}
exit = exit.min(r);
}
}
}
if enter > exit {
return None;
}
Some((
(from.0 + enter * dx, from.1 + enter * dy),
(from.0 + exit * dx, from.1 + exit * dy),
))
}