#[derive(Debug, Clone, Default)]
pub enum PolarMode {
#[default]
Scatter,
Line,
}
#[derive(Debug, Clone)]
pub struct PolarSeries {
pub r: Vec<f64>,
pub theta: Vec<f64>, pub label: Option<String>,
pub color: Option<String>,
pub mode: PolarMode,
pub marker_size: f64,
pub stroke_width: f64,
pub line_dash: Option<String>,
pub marker_opacity: Option<f64>,
pub marker_stroke_width: Option<f64>,
}
impl Default for PolarSeries {
fn default() -> Self {
PolarSeries {
r: Vec::new(),
theta: Vec::new(),
label: None,
color: None,
mode: PolarMode::Scatter,
marker_size: 5.0,
stroke_width: 1.5,
line_dash: None,
marker_opacity: None,
marker_stroke_width: None,
}
}
}
#[derive(Debug, Clone)]
pub struct PolarPlot {
pub series: Vec<PolarSeries>,
pub r_max: Option<f64>,
pub theta_start: f64,
pub clockwise: bool,
pub r_grid_lines: Option<usize>,
pub theta_divisions: usize,
pub show_grid: bool,
pub show_r_labels: bool,
pub show_legend: bool,
}
impl Default for PolarPlot {
fn default() -> Self {
PolarPlot {
series: Vec::new(),
r_max: None,
theta_start: 0.0,
clockwise: true,
r_grid_lines: None,
theta_divisions: 12,
show_grid: true,
show_r_labels: true,
show_legend: false,
}
}
}
impl PolarPlot {
pub fn new() -> Self {
Self::default()
}
pub fn with_series<T, U, IT, IU>(mut self, r: IT, theta: IU) -> Self
where
T: Into<f64>,
U: Into<f64>,
IT: IntoIterator<Item = T>,
IU: IntoIterator<Item = U>,
{
let r_vals: Vec<f64> = r.into_iter().map(Into::into).collect();
let theta_vals: Vec<f64> = theta.into_iter().map(Into::into).collect();
self.series.push(PolarSeries {
r: r_vals,
theta: theta_vals,
mode: PolarMode::Scatter,
..Default::default()
});
self
}
pub fn with_series_line<T, U, IT, IU>(mut self, r: IT, theta: IU) -> Self
where
T: Into<f64>,
U: Into<f64>,
IT: IntoIterator<Item = T>,
IU: IntoIterator<Item = U>,
{
let r_vals: Vec<f64> = r.into_iter().map(Into::into).collect();
let theta_vals: Vec<f64> = theta.into_iter().map(Into::into).collect();
self.series.push(PolarSeries {
r: r_vals,
theta: theta_vals,
mode: PolarMode::Line,
..Default::default()
});
self
}
pub fn with_series_labeled<S, T, U, IT, IU>(
mut self,
r: IT,
theta: IU,
label: S,
mode: PolarMode,
) -> Self
where
S: Into<String>,
T: Into<f64>,
U: Into<f64>,
IT: IntoIterator<Item = T>,
IU: IntoIterator<Item = U>,
{
let r_vals: Vec<f64> = r.into_iter().map(Into::into).collect();
let theta_vals: Vec<f64> = theta.into_iter().map(Into::into).collect();
self.series.push(PolarSeries {
r: r_vals,
theta: theta_vals,
label: Some(label.into()),
mode,
..Default::default()
});
self
}
pub fn with_r_max(mut self, r_max: f64) -> Self {
self.r_max = Some(r_max);
self
}
pub fn with_theta_start(mut self, degrees: f64) -> Self {
self.theta_start = degrees;
self
}
pub fn with_clockwise(mut self, cw: bool) -> Self {
self.clockwise = cw;
self
}
pub fn with_r_grid_lines(mut self, n: usize) -> Self {
self.r_grid_lines = Some(n);
self
}
pub fn with_theta_divisions(mut self, n: usize) -> Self {
self.theta_divisions = n;
self
}
pub fn with_grid(mut self, show: bool) -> Self {
self.show_grid = show;
self
}
pub fn with_r_labels(mut self, show: bool) -> Self {
self.show_r_labels = show;
self
}
pub fn with_legend(mut self, show: bool) -> Self {
self.show_legend = show;
self
}
pub fn with_color(mut self, color: impl Into<String>) -> Self {
if let Some(s) = self.series.last_mut() {
s.color = Some(color.into());
}
self
}
pub fn with_marker_opacity(mut self, opacity: f64) -> Self {
if let Some(s) = self.series.last_mut() {
s.marker_opacity = Some(opacity.clamp(0.0, 1.0));
}
self
}
pub fn with_marker_stroke_width(mut self, width: f64) -> Self {
if let Some(s) = self.series.last_mut() {
s.marker_stroke_width = Some(width);
}
self
}
pub fn r_max_auto(&self) -> f64 {
self.series
.iter()
.flat_map(|s| s.r.iter())
.cloned()
.fold(0.0_f64, f64::max)
}
}