use crate::vis::draw::rainbow;
use crate::vis::plotutils::{P64, R64};
use crate::vis::scene::{Color, Fill, Item, MarkerShape, Scene, Stroke, Viewport};
#[derive(Clone, Copy, Debug)]
pub enum MarkerScale {
CellFraction(f64),
Pixels(f64),
}
impl MarkerScale {
fn size(self, cell_w: f64, cell_px_w: f64) -> f64 {
match self {
MarkerScale::CellFraction(f) => f * cell_w,
MarkerScale::Pixels(px) => px * cell_w / cell_px_w,
}
}
}
fn add_cell(
scene: &mut Scene,
points: &[P64],
bounds: R64,
(dx, dy): (f64, f64),
marker_size: f64,
color: Color,
) {
let ((mn_x, mn_y), (mx_x, mx_y)) = bounds;
scene.push(Item::Polygon {
points: vec![
(dx + mn_x, dy + mn_y),
(dx + mx_x, dy + mn_y),
(dx + mx_x, dy + mx_y),
(dx + mn_x, dy + mx_y),
],
fill: None,
stroke: Some(Stroke::solid(
Color::rgb(180, 180, 180),
0.005 * (mx_x - mn_x),
)),
arrow: None,
});
scene.push(Item::Segment {
a: (dx + mn_x, dy),
b: (dx + mx_x, dy),
stroke: Stroke::solid(Color::rgb(225, 225, 225), 0.003 * (mx_x - mn_x)),
arrow: None,
});
scene.push(Item::Segment {
a: (dx, dy + mn_y),
b: (dx, dy + mx_y),
stroke: Stroke::solid(Color::rgb(225, 225, 225), 0.003 * (mx_x - mn_x)),
arrow: None,
});
let outline_width = marker_size * 0.10;
for &(x, y) in points {
scene.push(Item::Marker {
center: (x + dx, y + dy),
shape: MarkerShape::Circle,
size: marker_size,
fill: Some(Fill::solid(color)),
stroke: Some(Stroke::solid(Color::BLACK, outline_width)),
});
}
}
pub fn grid_png(
layers: &[Vec<P64>],
bounds: R64,
cols: usize,
img_w: u32,
marker: MarkerScale,
) -> Vec<u8> {
let n = layers.len();
let palette: Vec<Color> = rainbow(n.max(1), 1.0, 0.5);
let ((mn_x, mn_y), (mx_x, mx_y)) = bounds;
let cell_w = mx_x - mn_x;
let cell_h = mx_y - mn_y;
let cols = cols.max(1);
let rows = n.div_ceil(cols);
let gap = 0.05 * cell_w;
let cell_px_w = img_w as f64 / cols as f64;
let marker_size = marker.size(cell_w, cell_px_w);
let mut scene = Scene::new().with_background(Color::WHITE);
for (i, pts) in layers.iter().enumerate() {
let col = i % cols;
let row = i / cols;
let dx = col as f64 * (cell_w + gap) - mn_x;
let dy = (rows - 1 - row) as f64 * (cell_h + gap) - mn_y;
add_cell(&mut scene, pts, bounds, (dx, dy), marker_size, palette[i]);
}
let total_w = cols as f64 * cell_w + (cols.saturating_sub(1)) as f64 * gap;
let total_h = rows as f64 * cell_h + (rows.saturating_sub(1)) as f64 * gap;
let total_h_px = ((total_h / total_w) * img_w as f64).round().max(1.0) as u32;
let vp = Viewport::rect_for(img_w, total_h_px, ((0.0, 0.0), (total_w, total_h)), 16);
scene.to_png(&vp).expect("render PNG")
}
#[cfg(feature = "animation")]
pub fn animation_gif(
layers: &[Vec<P64>],
bounds: R64,
img_w: u32,
delay_ms: u16,
marker: MarkerScale,
) -> Vec<u8> {
let n = layers.len();
let palette: Vec<Color> = rainbow(n.max(1), 1.0, 0.5);
let ((mn_x, mn_y), (mx_x, mx_y)) = bounds;
let cell_w = mx_x - mn_x;
let cell_h = mx_y - mn_y;
let marker_size = marker.size(cell_w, img_w as f64);
let frames: Vec<Scene> = layers
.iter()
.enumerate()
.map(|(i, pts)| {
let mut frame = Scene::new().with_background(Color::WHITE);
add_cell(
&mut frame,
pts,
bounds,
(-mn_x, -mn_y),
marker_size,
palette[i],
);
frame
})
.collect();
let vp = Viewport::square_for(img_w, ((0.0, 0.0), (cell_w, cell_h)), 16);
crate::vis::animation::render_gif(&frames, &vp, delay_ms).expect("render GIF")
}