use super::{CoordLayout, CoordinateTrait, Rect};
use crate::error::ChartonError;
use crate::scale::{ExplicitTick, ScaleTrait};
use crate::theme::Theme;
use crate::visual::color::SingleColor;
use std::f64::consts::PI;
use std::sync::Arc;
pub struct Polar {
pub x_scale: Arc<dyn ScaleTrait>,
pub y_scale: Arc<dyn ScaleTrait>,
pub x_field: String,
pub y_field: String,
pub start_angle: f64,
pub end_angle: f64,
pub inner_radius: f64,
}
impl Polar {
pub fn new(
x_scale: Arc<dyn ScaleTrait>,
y_scale: Arc<dyn ScaleTrait>,
x_field: String,
y_field: String,
) -> Self {
Self {
x_scale,
y_scale,
x_field,
y_field,
start_angle: -PI / 2.0, end_angle: 3.0 * PI / 2.0,
inner_radius: 0.0, }
}
fn map_to_polar(&self, x_n: f64, y_n: f64) -> (f64, f64) {
let theta = self.start_angle + x_n * (self.end_angle - self.start_angle);
let r_norm = self.inner_radius + y_n * (1.0 - self.inner_radius);
(theta, r_norm)
}
}
impl CoordinateTrait for Polar {
fn render_axes(
&self,
svg: &mut String,
theme: &Theme,
panel: &Rect,
x_label: &str,
x_explicit: Option<&[ExplicitTick]>,
y_label: &str,
y_explicit: Option<&[ExplicitTick]>,
) -> Result<(), ChartonError> {
crate::render::polar_axis_renderer::render_polar_axes(
svg, theme, panel, self, x_label, x_explicit, y_label, y_explicit,
)
}
fn transform(&self, x_norm: f64, y_norm: f64, panel: &Rect) -> (f64, f64) {
let (theta, r_norm) = self.map_to_polar(x_norm, y_norm);
let center_x = panel.x + panel.width / 2.0;
let center_y = panel.y + panel.height / 2.0;
let max_r = panel.width.min(panel.height) / 2.0;
let r_px = r_norm * max_r;
let x_px = center_x + r_px * theta.cos();
let y_px = center_y + r_px * theta.sin();
(x_px, y_px)
}
fn transform_path(
&self,
points: &[(f64, f64)],
is_closed: bool,
panel: &Rect,
) -> Vec<(f64, f64)> {
if points.is_empty() {
return vec![];
}
let mut result = Vec::with_capacity(points.len() * 4);
let threshold = 0.01;
for i in 0..points.len() {
let p1 = points[i];
result.push(self.transform(p1.0, p1.1, panel));
let next_point = if i + 1 < points.len() {
Some(points[i + 1])
} else if is_closed {
Some(points[0])
} else {
None
};
if let Some(p2) = next_point {
let dx = (p2.0 - p1.0).abs();
if dx > threshold {
let steps = (dx / threshold).ceil() as usize;
for s in 1..steps {
let t = s as f64 / steps as f64;
result.push(self.transform(
p1.0 + (p2.0 - p1.0) * t,
p1.1 + (p2.1 - p1.1) * t,
panel,
));
}
}
}
}
result
}
fn get_x_arc(&self) -> Arc<dyn ScaleTrait> {
self.x_scale.clone()
}
fn get_y_arc(&self) -> Arc<dyn ScaleTrait> {
self.y_scale.clone()
}
fn get_x_scale(&self) -> &dyn ScaleTrait {
self.x_scale.as_ref()
}
fn get_y_scale(&self) -> &dyn ScaleTrait {
self.y_scale.as_ref()
}
fn get_x_label(&self) -> &str {
&self.x_field
}
fn get_y_label(&self) -> &str {
&self.y_field
}
fn is_flipped(&self) -> bool {
false
}
fn layout_hints(&self) -> CoordLayout {
CoordLayout {
default_bar_stroke: SingleColor::new("#E0E0E0"),
default_bar_stroke_width: 0.5,
default_bar_width: 1.0,
default_bar_spacing: 0.0,
default_bar_span: 1.0,
needs_interpolation: true,
}
}
}