use vello_cpu::kurbo::Point;
use crate::{layout::DataCoordinateSystem, pipeline::transform::TransformedSeries};
#[derive(Debug, Clone)]
pub enum MappedGeometry {
CartesianBar {
center_x: f64,
bottom_y: f64,
top_y: f64,
width: f64,
},
CartesianPoint { x: f64, y: f64 },
CartesianLine {
points: Vec<Point>,
area_baseline: Option<Vec<Point>>,
},
PolarSector {
center: Point,
inner_radius: f64,
outer_radius: f64,
start_angle: f64,
sweep_angle: f64,
},
}
pub trait CoordinateMapper {
fn map(
&self,
transformed: &TransformedSeries,
coord: &DataCoordinateSystem,
y_axis_index: usize,
) -> Vec<MappedGeometry>;
}
pub struct CartesianBarMapper {
pub bar_width_ratio: f64,
}
impl Default for CartesianBarMapper {
fn default() -> Self {
Self {
bar_width_ratio: 0.6,
}
}
}
impl CartesianBarMapper {
pub fn new() -> Self {
Self::default()
}
pub fn with_bar_width_ratio(mut self, ratio: f64) -> Self {
self.bar_width_ratio = ratio;
self
}
}
impl CoordinateMapper for CartesianBarMapper {
fn map(
&self,
transformed: &TransformedSeries,
coord: &DataCoordinateSystem,
y_axis_index: usize,
) -> Vec<MappedGeometry> {
let cat_width = coord.category_width();
let bar_width = cat_width * self.bar_width_ratio;
let y_range = coord.get_y_range(y_axis_index);
transformed
.items
.iter()
.map(|item| {
let center_x = coord.x_to_pixel(item.data_index as f64 + 0.5);
let top_y = coord.y_to_pixel(item.display_value, y_axis_index);
let baseline_value = if item.baseline < y_range.0 {
y_range.0
} else {
item.baseline
};
let bottom_y = coord.y_to_pixel(baseline_value, y_axis_index);
MappedGeometry::CartesianBar {
center_x,
bottom_y,
top_y,
width: bar_width,
}
})
.collect()
}
}
pub struct CartesianLineMapper {
pub smooth: bool,
pub has_area: bool,
}
impl CartesianLineMapper {
pub fn new(smooth: bool) -> Self {
Self {
smooth,
has_area: false,
}
}
pub fn with_area(mut self, has_area: bool) -> Self {
self.has_area = has_area;
self
}
}
impl CoordinateMapper for CartesianLineMapper {
fn map(
&self,
transformed: &TransformedSeries,
coord: &DataCoordinateSystem,
y_axis_index: usize,
) -> Vec<MappedGeometry> {
let points: Vec<Point> = transformed
.items
.iter()
.map(|item| {
let x = match item.original.x_value {
Some(xv) => coord.x_value_to_pixel(xv),
None => coord.x_to_pixel(item.data_index as f64 + 0.5),
};
Point::new(x, coord.y_to_pixel(item.display_value, y_axis_index))
})
.collect();
let area_baseline = if self.has_area {
let y_range = coord.get_y_range(y_axis_index);
let baseline_value = if transformed.items.iter().any(|item| item.baseline != 0.0) {
None
} else {
Some(y_range.0)
};
Some(
transformed
.items
.iter()
.map(|item| {
let y = match baseline_value {
Some(b) => coord.y_to_pixel(b, y_axis_index),
None => coord.y_to_pixel(item.baseline, y_axis_index),
};
let x = match item.original.x_value {
Some(xv) => coord.x_value_to_pixel(xv),
None => coord.x_to_pixel(item.data_index as f64 + 0.5),
};
Point::new(x, y)
})
.collect(),
)
} else {
None
};
vec![MappedGeometry::CartesianLine {
points,
area_baseline,
}]
}
}
pub struct PolarPieMapper {
pub center: (f64, f64),
pub radius: (f64, f64),
}
impl Default for PolarPieMapper {
fn default() -> Self {
Self {
center: (50.0, 50.0),
radius: (0.0, 75.0),
}
}
}
impl PolarPieMapper {
pub fn new() -> Self {
Self::default()
}
pub fn with_center(mut self, x: f64, y: f64) -> Self {
self.center = (x, y);
self
}
pub fn with_radius(mut self, inner: f64, outer: f64) -> Self {
self.radius = (inner, outer);
self
}
}
impl CoordinateMapper for PolarPieMapper {
fn map(
&self,
transformed: &TransformedSeries,
coord: &DataCoordinateSystem,
_y_axis_index: usize,
) -> Vec<MappedGeometry> {
let total: f64 = transformed
.items
.iter()
.map(|item| item.original.value)
.sum();
if total <= 0.0 {
return Vec::new();
}
let plot_bounds = coord.plot_bounds;
let center_x = plot_bounds.x0 + plot_bounds.width() * self.center.0 / 100.0;
let center_y = plot_bounds.y0 + plot_bounds.height() * self.center.1 / 100.0;
let center = Point::new(center_x, center_y);
let min_dim = plot_bounds.width().min(plot_bounds.height());
let inner_radius = min_dim / 2.0 * self.radius.0 / 100.0;
let outer_radius = min_dim / 2.0 * self.radius.1 / 100.0;
let mut start_angle = -std::f64::consts::PI / 2.0;
let mut geometries = Vec::new();
for item in &transformed.items {
let value = item.original.value;
let angle = (value / total) * 2.0 * std::f64::consts::PI;
geometries.push(MappedGeometry::PolarSector {
center,
inner_radius,
outer_radius,
start_angle,
sweep_angle: angle,
});
start_angle += angle;
}
geometries
}
}
pub struct CartesianScatterMapper;
impl Default for CartesianScatterMapper {
fn default() -> Self {
Self::new()
}
}
impl CartesianScatterMapper {
pub fn new() -> Self {
Self
}
}
impl CoordinateMapper for CartesianScatterMapper {
fn map(
&self,
transformed: &TransformedSeries,
coord: &DataCoordinateSystem,
y_axis_index: usize,
) -> Vec<MappedGeometry> {
transformed
.items
.iter()
.map(|item| {
let x = coord.x_to_pixel(item.data_index as f64 + 0.5);
let y = coord.y_to_pixel(item.display_value, y_axis_index);
MappedGeometry::CartesianPoint { x, y }
})
.collect()
}
}