use crate::artist::PolarArtist;
use crate::primitives::Color;
use crate::theme::Marker;
impl PolarArtist {
pub fn color(&mut self, color: Color) -> &mut Self {
self.color = color;
self
}
pub fn label(&mut self, label: &str) -> &mut Self {
self.label = Some(label.to_string());
self
}
pub fn alpha(&mut self, alpha: f64) -> &mut Self {
self.alpha = alpha.clamp(0.0, 1.0);
self
}
pub fn linewidth(&mut self, width: f64) -> &mut Self {
self.linewidth = width;
self
}
pub fn filled(&mut self, filled: bool) -> &mut Self {
self.filled = filled;
self
}
pub fn marker(&mut self, marker: Marker) -> &mut Self {
self.marker = Some(marker);
self
}
pub fn data_bounds(&self) -> (f64, f64, f64, f64) {
if self.r.is_empty() || self.theta.is_empty() {
return (-1.0, 1.0, -1.0, 1.0);
}
let r_max = self.max_finite_r();
if r_max <= 0.0 || !r_max.is_finite() {
return (-1.0, 1.0, -1.0, 1.0);
}
let extent = r_max * 1.1;
(-extent, extent, -extent, extent)
}
pub fn max_finite_r(&self) -> f64 {
self.r
.iter()
.copied()
.filter(|v| v.is_finite() && *v >= 0.0)
.fold(0.0_f64, f64::max)
}
pub fn polar_to_cartesian(r: f64, theta: f64) -> (f64, f64) {
(r * theta.cos(), r * theta.sin())
}
pub fn cartesian_points(&self) -> Vec<(f64, f64)> {
let n = self.r.len().min(self.theta.len());
(0..n)
.filter(|&i| self.r[i].is_finite() && self.theta[i].is_finite() && self.r[i] >= 0.0)
.map(|i| Self::polar_to_cartesian(self.r[i], self.theta[i]))
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::f64::consts::{FRAC_PI_2, PI, TAU};
fn sample_polar() -> PolarArtist {
PolarArtist {
theta: vec![0.0, FRAC_PI_2, PI, 3.0 * FRAC_PI_2],
r: vec![1.0, 2.0, 1.5, 0.5],
color: Color::TAB_BLUE,
label: None,
alpha: 1.0,
linewidth: 1.5,
filled: false,
marker: None,
}
}
#[test]
fn builder_color() {
let mut a = sample_polar();
a.color(Color::TAB_RED);
assert_eq!(a.color, Color::TAB_RED);
}
#[test]
fn builder_label() {
let mut a = sample_polar();
a.label("wind");
assert_eq!(a.label, Some("wind".to_string()));
}
#[test]
fn builder_alpha() {
let mut a = sample_polar();
a.alpha(0.5);
assert!((a.alpha - 0.5).abs() < f64::EPSILON);
}
#[test]
fn builder_alpha_clamped() {
let mut a = sample_polar();
a.alpha(1.5);
assert!((a.alpha - 1.0).abs() < f64::EPSILON);
a.alpha(-0.5);
assert!(a.alpha.abs() < f64::EPSILON);
}
#[test]
fn builder_linewidth() {
let mut a = sample_polar();
a.linewidth(3.0);
assert!((a.linewidth - 3.0).abs() < f64::EPSILON);
}
#[test]
fn builder_filled() {
let mut a = sample_polar();
assert!(!a.filled);
a.filled(true);
assert!(a.filled);
}
#[test]
fn builder_marker() {
let mut a = sample_polar();
assert!(a.marker.is_none());
a.marker(Marker::Circle);
assert_eq!(a.marker, Some(Marker::Circle));
}
#[test]
fn data_bounds_basic() {
let a = sample_polar();
let (xmin, xmax, ymin, ymax) = a.data_bounds();
assert!((xmin - (-2.2)).abs() < 1e-10);
assert!((xmax - 2.2).abs() < 1e-10);
assert!((ymin - (-2.2)).abs() < 1e-10);
assert!((ymax - 2.2).abs() < 1e-10);
}
#[test]
fn data_bounds_empty() {
let a = PolarArtist {
theta: vec![],
r: vec![],
color: Color::TAB_BLUE,
label: None,
alpha: 1.0,
linewidth: 1.5,
filled: false,
marker: None,
};
assert_eq!(a.data_bounds(), (-1.0, 1.0, -1.0, 1.0));
}
#[test]
fn data_bounds_all_nan() {
let a = PolarArtist {
theta: vec![0.0, 1.0],
r: vec![f64::NAN, f64::NAN],
color: Color::TAB_BLUE,
label: None,
alpha: 1.0,
linewidth: 1.5,
filled: false,
marker: None,
};
assert_eq!(a.data_bounds(), (-1.0, 1.0, -1.0, 1.0));
}
#[test]
fn polar_to_cartesian_at_zero() {
let (x, y) = PolarArtist::polar_to_cartesian(1.0, 0.0);
assert!((x - 1.0).abs() < 1e-10);
assert!(y.abs() < 1e-10);
}
#[test]
fn polar_to_cartesian_at_90_deg() {
let (x, y) = PolarArtist::polar_to_cartesian(2.0, FRAC_PI_2);
assert!(x.abs() < 1e-10);
assert!((y - 2.0).abs() < 1e-10);
}
#[test]
fn polar_to_cartesian_at_pi() {
let (x, y) = PolarArtist::polar_to_cartesian(1.0, PI);
assert!((x - (-1.0)).abs() < 1e-10);
assert!(y.abs() < 1e-10);
}
#[test]
fn polar_to_cartesian_at_270_deg() {
let (x, y) = PolarArtist::polar_to_cartesian(3.0, 3.0 * FRAC_PI_2);
assert!(x.abs() < 1e-10);
assert!((y - (-3.0)).abs() < 1e-10);
}
#[test]
fn cartesian_points_basic() {
let a = PolarArtist {
theta: vec![0.0, FRAC_PI_2],
r: vec![1.0, 2.0],
color: Color::TAB_BLUE,
label: None,
alpha: 1.0,
linewidth: 1.5,
filled: false,
marker: None,
};
let pts = a.cartesian_points();
assert_eq!(pts.len(), 2);
assert!((pts[0].0 - 1.0).abs() < 1e-10);
assert!(pts[0].1.abs() < 1e-10);
assert!(pts[1].0.abs() < 1e-10);
assert!((pts[1].1 - 2.0).abs() < 1e-10);
}
#[test]
fn cartesian_points_nan_filtered() {
let a = PolarArtist {
theta: vec![0.0, f64::NAN, PI],
r: vec![1.0, 2.0, f64::NAN],
color: Color::TAB_BLUE,
label: None,
alpha: 1.0,
linewidth: 1.5,
filled: false,
marker: None,
};
let pts = a.cartesian_points();
assert_eq!(pts.len(), 1);
assert!((pts[0].0 - 1.0).abs() < 1e-10);
}
#[test]
fn negative_angles() {
let (x, y) = PolarArtist::polar_to_cartesian(1.0, -FRAC_PI_2);
assert!(x.abs() < 1e-10);
assert!((y - (-1.0)).abs() < 1e-10);
}
#[test]
fn angles_greater_than_two_pi() {
let (x1, y1) = PolarArtist::polar_to_cartesian(1.0, TAU + FRAC_PI_2);
let (x2, y2) = PolarArtist::polar_to_cartesian(1.0, FRAC_PI_2);
assert!((x1 - x2).abs() < 1e-10);
assert!((y1 - y2).abs() < 1e-10);
}
#[test]
fn max_finite_r_ignores_nan_and_negative() {
let a = PolarArtist {
theta: vec![0.0, 1.0, 2.0, 3.0],
r: vec![f64::NAN, -1.0, 5.0, 3.0],
color: Color::TAB_BLUE,
label: None,
alpha: 1.0,
linewidth: 1.5,
filled: false,
marker: None,
};
assert!((a.max_finite_r() - 5.0).abs() < 1e-10);
}
#[test]
fn data_bounds_with_single_point() {
let a = PolarArtist {
theta: vec![0.0],
r: vec![3.0],
color: Color::TAB_BLUE,
label: None,
alpha: 1.0,
linewidth: 1.5,
filled: false,
marker: None,
};
let (xmin, xmax, ymin, ymax) = a.data_bounds();
assert!((xmin - (-3.3)).abs() < 1e-10);
assert!((xmax - 3.3).abs() < 1e-10);
assert!((ymin - (-3.3)).abs() < 1e-10);
assert!((ymax - 3.3).abs() < 1e-10);
}
#[test]
fn builder_chaining_returns_self() {
let mut a = sample_polar();
let _ = a
.color(Color::TAB_GREEN)
.label("test")
.alpha(0.7)
.linewidth(2.5)
.filled(true)
.marker(Marker::Diamond);
assert_eq!(a.color, Color::TAB_GREEN);
assert_eq!(a.label, Some("test".to_string()));
assert!((a.alpha - 0.7).abs() < f64::EPSILON);
assert!((a.linewidth - 2.5).abs() < f64::EPSILON);
assert!(a.filled);
assert_eq!(a.marker, Some(Marker::Diamond));
}
}