use super::data::{PlotData, ReactiveValue};
use crate::render::{Color, LineStyle, MarkerStyle};
pub trait IntoPlot: Sized {
fn into_plot(self) -> super::Plot;
fn as_plot(&self) -> &super::Plot;
}
impl IntoPlot for super::Plot {
fn into_plot(self) -> super::Plot {
self
}
fn as_plot(&self) -> &super::Plot {
self
}
}
macro_rules! impl_terminal_methods {
($config:ty) => {
impl PlotBuilder<$config> {
#[cfg(not(target_arch = "wasm32"))]
pub fn save<P: AsRef<std::path::Path>>(self, path: P) -> crate::core::Result<()> {
self.finalize().save(path)
}
pub fn render(self) -> crate::core::Result<super::Image> {
self.finalize().render()
}
pub fn render_png_bytes(self) -> crate::core::Result<Vec<u8>> {
self.finalize().render_png_bytes()
}
pub fn render_to_svg(self) -> crate::core::Result<String> {
self.finalize().render_to_svg()
}
#[cfg(not(target_arch = "wasm32"))]
pub fn export_svg<P: AsRef<std::path::Path>>(self, path: P) -> crate::core::Result<()> {
self.finalize().export_svg(path)
}
#[cfg(all(feature = "pdf", not(target_arch = "wasm32")))]
pub fn save_pdf<P: AsRef<std::path::Path>>(self, path: P) -> crate::core::Result<()> {
self.finalize().save_pdf(path)
}
#[cfg(not(target_arch = "wasm32"))]
pub fn save_with_size<P: AsRef<std::path::Path>>(
self,
path: P,
width: u32,
height: u32,
) -> crate::core::Result<()> {
self.finalize().save_with_size(path, width, height)
}
impl_series_continuation_methods!(self.finalize());
pub fn legend_position(self, position: crate::core::LegendPosition) -> super::Plot {
self.finalize().legend_position(position)
}
#[deprecated(
since = "0.8.0",
note = "Not needed - series finalize automatically. Use .save() directly."
)]
pub fn end_series(self) -> super::Plot {
self.finalize()
}
}
impl From<PlotBuilder<$config>> for super::Plot {
fn from(builder: PlotBuilder<$config>) -> super::Plot {
builder.finalize()
}
}
impl IntoPlot for PlotBuilder<$config> {
fn into_plot(self) -> super::Plot {
self.finalize()
}
fn as_plot(&self) -> &super::Plot {
&self.plot
}
}
};
}
macro_rules! impl_inset_builder_methods {
($(($config:ty, $series_name:literal)),+ $(,)?) => {
$(
impl PlotBuilder<$config> {
/// Override inset placement for mixed Cartesian/non-Cartesian plots.
pub fn inset_layout(mut self, layout: super::InsetLayout) -> Self {
self.style.inset_layout = Some(layout.normalized());
self
}
#[doc = concat!(
"Set the inset anchor used when this ",
$series_name,
" is rendered inside a mixed plot."
)]
pub fn inset_anchor(mut self, anchor: super::InsetAnchor) -> Self {
let mut layout = self.style.inset_layout.unwrap_or_default();
layout.anchor = anchor;
self.style.inset_layout = Some(layout.normalized());
self
}
pub fn inset_size_frac(mut self, width_frac: f32, height_frac: f32) -> Self {
let mut layout = self.style.inset_layout.unwrap_or_default();
layout.width_frac = width_frac;
layout.height_frac = height_frac;
self.style.inset_layout = Some(layout.normalized());
self
}
pub fn inset_margin_pt(mut self, margin_pt: f32) -> Self {
let mut layout = self.style.inset_layout.unwrap_or_default();
layout.margin_pt = margin_pt;
self.style.inset_layout = Some(layout.normalized());
self
}
}
)+
};
}
#[derive(Clone, Debug)]
pub enum PlotInput {
Single(Vec<f64>),
XY(Vec<f64>, Vec<f64>),
XYSource(super::PlotData, super::PlotData),
Grid2D {
x: Vec<f64>,
y: Vec<f64>,
z: Vec<Vec<f64>>,
},
Categorical {
categories: Vec<String>,
values: Vec<f64>,
},
CategoricalSource {
categories: Vec<String>,
values: super::PlotData,
},
}
impl PlotInput {
pub fn point_count(&self) -> usize {
match self {
PlotInput::Single(data) => data.len(),
PlotInput::XY(x, _) => x.len(),
PlotInput::XYSource(x, _) => x.len(),
PlotInput::Grid2D { x, y, .. } => x.len() * y.len(),
PlotInput::Categorical { values, .. } => values.len(),
PlotInput::CategoricalSource { values, .. } => values.len(),
}
}
}
#[derive(Clone, Debug, Default)]
pub struct SeriesStyle {
pub label: Option<String>,
pub color: Option<Color>,
pub color_source: Option<ReactiveValue<Color>>,
pub line_width: Option<f32>,
pub line_width_source: Option<ReactiveValue<f32>>,
pub line_style: Option<LineStyle>,
pub line_style_source: Option<ReactiveValue<LineStyle>>,
pub marker_style: Option<MarkerStyle>,
pub marker_style_source: Option<ReactiveValue<MarkerStyle>>,
pub marker_size: Option<f32>,
pub marker_size_source: Option<ReactiveValue<f32>>,
pub alpha: Option<f32>,
pub alpha_source: Option<ReactiveValue<f32>>,
pub y_errors: Option<crate::plots::error::ErrorValues>,
pub x_errors: Option<crate::plots::error::ErrorValues>,
pub error_config: Option<crate::plots::error::ErrorBarConfig>,
pub inset_layout: Option<super::InsetLayout>,
}
impl SeriesStyle {
pub(crate) fn set_color_source_value(&mut self, color: ReactiveValue<Color>) {
match color {
ReactiveValue::Static(color) => {
self.color = Some(color);
self.color_source = None;
}
source => {
self.color = None;
self.color_source = Some(source);
}
}
}
pub(crate) fn set_line_width_source_value(&mut self, width: ReactiveValue<f32>) {
match width {
ReactiveValue::Static(width) => {
self.line_width = Some(width.max(0.1));
self.line_width_source = None;
}
source => {
self.line_width = None;
self.line_width_source = Some(source);
}
}
}
pub(crate) fn set_line_style_source_value(&mut self, style: ReactiveValue<LineStyle>) {
match style {
ReactiveValue::Static(style) => {
self.line_style = Some(style);
self.line_style_source = None;
}
source => {
self.line_style = None;
self.line_style_source = Some(source);
}
}
}
pub(crate) fn set_marker_style_source_value(&mut self, style: ReactiveValue<MarkerStyle>) {
match style {
ReactiveValue::Static(style) => {
self.marker_style = Some(style);
self.marker_style_source = None;
}
source => {
self.marker_style = None;
self.marker_style_source = Some(source);
}
}
}
pub(crate) fn set_marker_size_source_value(&mut self, size: ReactiveValue<f32>) {
match size {
ReactiveValue::Static(size) => {
self.marker_size = Some(size.max(0.1));
self.marker_size_source = None;
}
source => {
self.marker_size = None;
self.marker_size_source = Some(source);
}
}
}
pub(crate) fn set_alpha_source_value(&mut self, alpha: ReactiveValue<f32>) {
match alpha {
ReactiveValue::Static(alpha) => {
self.alpha = Some(alpha.clamp(0.0, 1.0));
self.alpha_source = None;
}
source => {
self.alpha = None;
self.alpha_source = Some(source);
}
}
}
}
#[derive(Debug, Clone)]
pub struct PlotBuilder<C>
where
C: crate::plots::PlotConfig + Clone,
{
pub(crate) plot: super::Plot,
pub(crate) input: PlotInput,
pub(crate) config: C,
pub(crate) style: SeriesStyle,
}
impl<C> PlotBuilder<C>
where
C: crate::plots::PlotConfig,
{
pub(crate) fn new(plot: super::Plot, input: PlotInput, config: C) -> Self {
Self {
plot,
input,
config,
style: SeriesStyle::default(),
}
}
pub fn label<S: Into<String>>(mut self, label: S) -> Self {
self.style.label = Some(label.into());
self
}
pub fn color(mut self, color: Color) -> Self {
self.style.color = Some(color);
self.style.color_source = None;
self
}
pub fn color_source<S>(mut self, color: S) -> Self
where
S: Into<ReactiveValue<Color>>,
{
self.style.set_color_source_value(color.into());
self
}
pub fn line_width(mut self, width: f32) -> Self {
self.style.line_width = Some(width.max(0.1));
self.style.line_width_source = None;
self
}
pub fn line_width_source<S>(mut self, width: S) -> Self
where
S: Into<ReactiveValue<f32>>,
{
self.style.set_line_width_source_value(width.into());
self
}
pub fn line_style(mut self, style: LineStyle) -> Self {
self.style.line_style = Some(style);
self.style.line_style_source = None;
self
}
pub fn line_style_source<S>(mut self, style: S) -> Self
where
S: Into<ReactiveValue<LineStyle>>,
{
self.style.set_line_style_source_value(style.into());
self
}
pub fn alpha(mut self, alpha: f32) -> Self {
self.style.alpha = Some(alpha.clamp(0.0, 1.0));
self.style.alpha_source = None;
self
}
pub fn alpha_source<S>(mut self, alpha: S) -> Self
where
S: Into<ReactiveValue<f32>>,
{
self.style.set_alpha_source_value(alpha.into());
self
}
pub fn with_yerr<E: crate::data::NumericData1D>(mut self, errors: &E) -> Self {
match crate::data::collect_numeric_data_1d(errors, self.plot.null_policy) {
Ok(values) => {
self.style.y_errors = Some(crate::plots::error::ErrorValues::symmetric(values));
}
Err(err) => {
self.plot.set_pending_ingestion_error(err);
}
}
self
}
pub fn with_xerr<E: crate::data::NumericData1D>(mut self, errors: &E) -> Self {
match crate::data::collect_numeric_data_1d(errors, self.plot.null_policy) {
Ok(values) => {
self.style.x_errors = Some(crate::plots::error::ErrorValues::symmetric(values));
}
Err(err) => {
self.plot.set_pending_ingestion_error(err);
}
}
self
}
pub fn with_yerr_asymmetric<E1, E2>(mut self, lower: &E1, upper: &E2) -> Self
where
E1: crate::data::NumericData1D,
E2: crate::data::NumericData1D,
{
let lower_values = crate::data::collect_numeric_data_1d(lower, self.plot.null_policy);
let upper_values = crate::data::collect_numeric_data_1d(upper, self.plot.null_policy);
match (lower_values, upper_values) {
(Ok(lower), Ok(upper)) => {
self.style.y_errors =
Some(crate::plots::error::ErrorValues::asymmetric(lower, upper));
}
(Err(err), _) | (_, Err(err)) => {
self.plot.set_pending_ingestion_error(err);
}
}
self
}
pub fn with_xerr_asymmetric<E1, E2>(mut self, lower: &E1, upper: &E2) -> Self
where
E1: crate::data::NumericData1D,
E2: crate::data::NumericData1D,
{
let lower_values = crate::data::collect_numeric_data_1d(lower, self.plot.null_policy);
let upper_values = crate::data::collect_numeric_data_1d(upper, self.plot.null_policy);
match (lower_values, upper_values) {
(Ok(lower), Ok(upper)) => {
self.style.x_errors =
Some(crate::plots::error::ErrorValues::asymmetric(lower, upper));
}
(Err(err), _) | (_, Err(err)) => {
self.plot.set_pending_ingestion_error(err);
}
}
self
}
pub fn error_config(mut self, config: crate::plots::error::ErrorBarConfig) -> Self {
self.style.error_config = Some(config);
self
}
pub fn title(mut self, title: impl Into<super::PlotText>) -> Self {
self.plot = self.plot.title(title);
self
}
pub fn xlabel(mut self, label: impl Into<super::PlotText>) -> Self {
self.plot = self.plot.xlabel(label);
self
}
pub fn ylabel(mut self, label: impl Into<super::PlotText>) -> Self {
self.plot = self.plot.ylabel(label);
self
}
pub fn null_policy(mut self, policy: crate::data::NullPolicy) -> Self {
self.plot = self.plot.null_policy(policy);
self
}
pub fn legend_best(mut self) -> Self {
self.plot = self.plot.legend_best();
self
}
pub fn legend(mut self, position: crate::core::Position) -> Self {
self.plot = self.plot.legend(position);
self
}
pub fn legend_font_size(mut self, size: f32) -> Self {
self.plot = self.plot.legend_font_size(size);
self
}
pub fn legend_corner_radius(mut self, radius: f32) -> Self {
self.plot = self.plot.legend_corner_radius(radius);
self
}
pub fn legend_columns(mut self, columns: usize) -> Self {
self.plot = self.plot.legend_columns(columns);
self
}
pub fn size(mut self, width: f32, height: f32) -> Self {
self.plot = self.plot.size(width, height);
self
}
pub fn size_px(mut self, width: u32, height: u32) -> Self {
self.plot = self.plot.size_px(width, height);
self
}
pub fn dpi(mut self, dpi: u32) -> Self {
self.plot = self.plot.dpi(dpi);
self
}
pub fn max_resolution(mut self, max_width: u32, max_height: u32) -> Self {
self.plot = self.plot.max_resolution(max_width, max_height);
self
}
pub fn xlim(mut self, min: f64, max: f64) -> Self {
self.plot = self.plot.xlim(min, max);
self
}
pub fn ylim(mut self, min: f64, max: f64) -> Self {
self.plot = self.plot.ylim(min, max);
self
}
pub fn grid(mut self, enabled: bool) -> Self {
self.plot = self.plot.grid(enabled);
self
}
pub fn ticks(mut self, enabled: bool) -> Self {
self.plot = self.plot.ticks(enabled);
self
}
pub fn tick_direction_inside(mut self) -> Self {
self.plot = self.plot.tick_direction_inside();
self
}
pub fn tick_direction_outside(mut self) -> Self {
self.plot = self.plot.tick_direction_outside();
self
}
pub fn tick_direction_inout(mut self) -> Self {
self.plot = self.plot.tick_direction_inout();
self
}
pub fn tick_sides(mut self, sides: crate::core::TickSides) -> Self {
self.plot = self.plot.tick_sides(sides);
self
}
pub fn ticks_all_sides(mut self) -> Self {
self.plot = self.plot.ticks_all_sides();
self
}
pub fn ticks_bottom_left(mut self) -> Self {
self.plot = self.plot.ticks_bottom_left();
self
}
pub fn show_top_ticks(mut self, enabled: bool) -> Self {
self.plot = self.plot.show_top_ticks(enabled);
self
}
pub fn show_bottom_ticks(mut self, enabled: bool) -> Self {
self.plot = self.plot.show_bottom_ticks(enabled);
self
}
pub fn show_left_ticks(mut self, enabled: bool) -> Self {
self.plot = self.plot.show_left_ticks(enabled);
self
}
pub fn show_right_ticks(mut self, enabled: bool) -> Self {
self.plot = self.plot.show_right_ticks(enabled);
self
}
#[cfg(feature = "typst-math")]
#[cfg_attr(docsrs, doc(cfg(feature = "typst-math")))]
pub fn typst(mut self, enabled: bool) -> Self {
self.plot = self.plot.typst(enabled);
self
}
pub fn theme(mut self, theme: crate::render::Theme) -> Self {
self.plot = self.plot.theme(theme);
self
}
pub fn auto_optimize(mut self) -> Self {
let current_points = self.input.point_count();
self.plot = self.plot.auto_optimize_with_extra_points(current_points);
self
}
pub fn xscale(mut self, scale: crate::axes::AxisScale) -> Self {
self.plot = self.plot.xscale(scale);
self
}
pub fn yscale(mut self, scale: crate::axes::AxisScale) -> Self {
self.plot = self.plot.yscale(scale);
self
}
pub fn backend(mut self, backend: super::BackendType) -> Self {
self.plot = self.plot.backend(backend);
self
}
#[cfg(feature = "gpu")]
pub fn gpu(mut self, enabled: bool) -> Self {
self.plot = self.plot.gpu(enabled);
self
}
pub fn get_backend_name(&self) -> &'static str {
self.plot.get_backend_name()
}
pub fn get_config(&self) -> &C {
&self.config
}
pub fn get_config_mut(&mut self) -> &mut C {
&mut self.config
}
pub fn get_plot(&self) -> &super::Plot {
&self.plot
}
pub fn annotate(mut self, annotation: crate::core::Annotation) -> Self {
self.plot = self.plot.annotate(annotation);
self
}
pub fn arrow(mut self, x1: f64, y1: f64, x2: f64, y2: f64) -> Self {
self.plot = self.plot.arrow(x1, y1, x2, y2);
self
}
pub fn arrow_styled(
mut self,
x1: f64,
y1: f64,
x2: f64,
y2: f64,
style: crate::core::ArrowStyle,
) -> Self {
self.plot = self.plot.arrow_styled(x1, y1, x2, y2, style);
self
}
pub fn text<S: Into<String>>(mut self, x: f64, y: f64, text: S) -> Self {
self.plot = self.plot.text(x, y, text);
self
}
pub fn text_styled<S: Into<String>>(
mut self,
x: f64,
y: f64,
text: S,
style: crate::core::TextStyle,
) -> Self {
self.plot = self.plot.text_styled(x, y, text, style);
self
}
pub fn hline(mut self, y: f64) -> Self {
self.plot = self.plot.hline(y);
self
}
pub fn hline_styled(mut self, y: f64, color: Color, width: f32, style: LineStyle) -> Self {
self.plot = self.plot.hline_styled(y, color, width, style);
self
}
pub fn vline(mut self, x: f64) -> Self {
self.plot = self.plot.vline(x);
self
}
pub fn vline_styled(mut self, x: f64, color: Color, width: f32, style: LineStyle) -> Self {
self.plot = self.plot.vline_styled(x, color, width, style);
self
}
pub fn rect(mut self, x: f64, y: f64, width: f64, height: f64) -> Self {
self.plot = self.plot.rect(x, y, width, height);
self
}
pub fn rect_styled(
mut self,
x: f64,
y: f64,
width: f64,
height: f64,
style: crate::core::ShapeStyle,
) -> Self {
self.plot = self.plot.rect_styled(x, y, width, height, style);
self
}
pub fn fill_between(mut self, x: &[f64], y1: &[f64], y2: &[f64]) -> Self {
self.plot = self.plot.fill_between(x, y1, y2);
self
}
pub fn fill_to_baseline(mut self, x: &[f64], y: &[f64], baseline: f64) -> Self {
self.plot = self.plot.fill_to_baseline(x, y, baseline);
self
}
pub fn fill_between_styled(
mut self,
x: &[f64],
y1: &[f64],
y2: &[f64],
style: crate::core::FillStyle,
where_positive: bool,
) -> Self {
self.plot = self
.plot
.fill_between_styled(x, y1, y2, style, where_positive);
self
}
pub fn axvspan(mut self, x_min: f64, x_max: f64) -> Self {
self.plot = self.plot.axvspan(x_min, x_max);
self
}
pub fn axhspan(mut self, y_min: f64, y_max: f64) -> Self {
self.plot = self.plot.axhspan(y_min, y_max);
self
}
}
impl PlotBuilder<crate::plots::KdeConfig> {
pub fn bandwidth(mut self, bw: f64) -> Self {
self.config.bandwidth = Some(bw);
self
}
pub fn n_points(mut self, n: usize) -> Self {
self.config.n_points = n.max(10);
self
}
pub fn fill(mut self, fill: bool) -> Self {
self.config.fill = fill;
self
}
pub fn fill_alpha(mut self, alpha: f32) -> Self {
self.config.fill_alpha = alpha.clamp(0.0, 1.0);
self
}
pub fn kde_line_width(mut self, width: f32) -> Self {
self.config.line_width = width.max(0.1);
self
}
pub fn cumulative(mut self, cumulative: bool) -> Self {
self.config.cumulative = cumulative;
self
}
pub fn clip(mut self, min: f64, max: f64) -> Self {
self.config.clip = Some((min, max));
self
}
pub fn vertical_line(mut self, x: f64) -> Self {
self.config.vertical_lines.push(x);
self
}
fn finalize(self) -> super::Plot {
let data = match &self.input {
PlotInput::Single(d) => d.clone(),
_ => vec![], };
let kde_data = crate::plots::compute_kde(&data, &self.config);
self.plot.add_kde_series(kde_data, self.style)
}
}
impl_terminal_methods!(crate::plots::KdeConfig);
impl PlotBuilder<crate::plots::EcdfConfig> {
pub fn stat(mut self, stat: crate::plots::EcdfStat) -> Self {
self.config.stat = stat;
self
}
pub fn complementary(mut self, comp: bool) -> Self {
self.config.complementary = comp;
self
}
pub fn show_ci(mut self, show: bool) -> Self {
self.config.show_ci = show;
self
}
pub fn ci_level(mut self, level: f64) -> Self {
self.config.ci_level = level.clamp(0.0, 1.0);
self
}
pub fn show_markers(mut self, show: bool) -> Self {
self.config.show_markers = show;
self
}
pub fn marker_size(mut self, size: f32) -> Self {
self.config.marker_size = size.max(0.1);
self
}
pub fn ecdf_line_width(mut self, width: f32) -> Self {
self.config.line_width = width.max(0.1);
self
}
fn finalize(self) -> super::Plot {
let data = match &self.input {
PlotInput::Single(d) => d.clone(),
_ => vec![], };
let ecdf_data = crate::plots::compute_ecdf(&data, &self.config);
self.plot.add_ecdf_series(ecdf_data, self.style)
}
}
impl_terminal_methods!(crate::plots::EcdfConfig);
impl PlotBuilder<crate::plots::ContourConfig> {
pub fn levels(mut self, n: usize) -> Self {
self.config.n_levels = n.max(2);
self
}
pub fn level_values(mut self, levels: Vec<f64>) -> Self {
self.config.levels = Some(levels);
self
}
pub fn filled(mut self, filled: bool) -> Self {
self.config.filled = filled;
self
}
pub fn show_lines(mut self, show: bool) -> Self {
self.config.show_lines = show;
self
}
pub fn show_labels(mut self, show: bool) -> Self {
self.config.show_labels = show;
self
}
pub fn colormap_name(mut self, name: &str) -> Self {
self.config.cmap = name.to_string();
self
}
pub fn contour_line_width(mut self, width: f32) -> Self {
self.config.line_width = width.max(0.1);
self
}
pub fn smooth(mut self, method: crate::plots::ContourInterpolation, factor: usize) -> Self {
self.config.interpolation = method;
self.config.interpolation_factor = factor.max(1);
self
}
pub fn colorbar(mut self, show: bool) -> Self {
self.config.colorbar = show;
self
}
pub fn colorbar_label(mut self, label: &str) -> Self {
self.config.colorbar_label = Some(label.to_string());
self
}
fn finalize(self) -> super::Plot {
let (x, y, z) = match &self.input {
PlotInput::Grid2D { x, y, z } => (x.clone(), y.clone(), z.clone()),
_ => (vec![], vec![], vec![]),
};
let z_flat: Vec<f64> = z.iter().flat_map(|row| row.iter().copied()).collect();
let contour_data = crate::plots::compute_contour_plot(&x, &y, &z_flat, &self.config);
self.plot.add_contour_series(contour_data, self.style)
}
}
impl_terminal_methods!(crate::plots::ContourConfig);
impl_inset_builder_methods!(
(crate::plots::PieConfig, "pie chart"),
(crate::plots::RadarConfig, "radar chart"),
(crate::plots::PolarPlotConfig, "polar plot"),
);
impl PlotBuilder<crate::plots::PieConfig> {
pub fn labels<S: AsRef<str>>(mut self, labels: &[S]) -> Self {
self.config.labels = labels.iter().map(|s| s.as_ref().to_string()).collect();
self
}
pub fn explode(mut self, explode: &[f64]) -> Self {
self.config.explode = explode.to_vec();
self
}
pub fn donut(mut self, ratio: f64) -> Self {
self.config.inner_radius = ratio.clamp(0.0, 0.95);
self
}
pub fn start_angle(mut self, degrees: f64) -> Self {
self.config.start_angle = degrees;
self
}
pub fn show_percentages(mut self, show: bool) -> Self {
self.config.show_percentages = show;
self
}
pub fn show_values(mut self, show: bool) -> Self {
self.config.show_values = show;
self
}
pub fn show_labels(mut self, show: bool) -> Self {
self.config.show_labels = show;
self
}
pub fn shadow(mut self, offset: f64) -> Self {
self.config.shadow = offset.max(0.0);
self
}
pub fn font_size(mut self, size: f32) -> Self {
self.config.label_font_size = size;
self
}
pub fn label_distance(mut self, distance: f64) -> Self {
self.config.label_distance = distance;
self
}
pub fn clockwise(mut self) -> Self {
self.config.counter_clockwise = false;
self
}
fn finalize(self) -> super::Plot {
let values = match &self.input {
PlotInput::Single(v) => v.clone(),
_ => vec![],
};
let pie_data = crate::plots::composition::pie::PieData::compute(&values, &self.config);
self.plot.add_pie_series(pie_data, self.style)
}
}
impl_terminal_methods!(crate::plots::PieConfig);
impl PlotBuilder<crate::plots::RadarConfig> {
pub fn series<V: crate::data::Data1D<f64>>(mut self, values: &V) -> Self {
let values_vec: Vec<f64> = (0..values.len())
.filter_map(|i| values.get(i).copied())
.collect();
if let Some(label) = self.style.label.take() {
if let Some(last) = self.config.series_labels.last_mut() {
if last.is_empty() {
*last = label;
}
}
}
match &mut self.input {
PlotInput::Single(data) => {
if !data.is_empty() {
data.push(f64::NAN); }
data.extend(values_vec);
}
_ => {
self.input = PlotInput::Single(values_vec);
}
}
self.config.series_labels.push(String::new());
self
}
pub fn series_label(mut self, name: &str) -> Self {
if let Some(last) = self.config.series_labels.last_mut() {
*last = name.to_string();
}
self.style.label = Some(name.to_string());
self
}
pub fn add_series<S, V>(mut self, name: S, values: &V) -> Self
where
S: Into<String>,
V: crate::data::Data1D<f64>,
{
let values_vec: Vec<f64> = (0..values.len())
.filter_map(|i| values.get(i).copied())
.collect();
let name_string = name.into();
self.config.series_labels.push(name_string);
if self.config.colors.is_none() {
self.config.colors = Some(vec![]);
}
if let Some(ref mut colors) = self.config.colors {
colors.push(Color::TRANSPARENT); }
self.config.per_series_fill_alphas.push(None);
self.config.per_series_line_widths.push(None);
let series_idx = self.config.series_labels.len() - 1;
self.config.current_series_idx = Some(series_idx);
match &mut self.input {
PlotInput::Single(data) => {
if !data.is_empty() {
data.push(f64::NAN); }
data.extend(values_vec);
}
_ => {
self.input = PlotInput::Single(values_vec);
}
}
self
}
pub fn with_color(mut self, color: Color) -> Self {
if let Some(idx) = self.config.current_series_idx {
if let Some(ref mut colors) = self.config.colors {
if let Some(c) = colors.get_mut(idx) {
*c = color;
}
}
}
self
}
pub fn with_fill_alpha(mut self, alpha: f32) -> Self {
if let Some(idx) = self.config.current_series_idx {
if let Some(a) = self.config.per_series_fill_alphas.get_mut(idx) {
*a = Some(alpha.clamp(0.0, 1.0));
}
}
self
}
pub fn with_line_width(mut self, width: f32) -> Self {
if let Some(idx) = self.config.current_series_idx {
if let Some(w) = self.config.per_series_line_widths.get_mut(idx) {
*w = Some(width.max(0.1));
}
}
self
}
pub fn fill_alpha(mut self, alpha: f32) -> Self {
self.config.fill_alpha = alpha.clamp(0.0, 1.0);
self
}
pub fn rings(mut self, n: usize) -> Self {
self.config.grid_rings = n.max(1);
self
}
pub fn fill(mut self, fill: bool) -> Self {
self.config.fill = fill;
self
}
pub fn radar_line_width(mut self, width: f32) -> Self {
self.config.line_width = width.max(0.1);
self
}
pub fn show_axis_labels(mut self, show: bool) -> Self {
self.config.show_axis_labels = show;
self
}
fn finalize(mut self) -> super::Plot {
if let Some(label) = self.style.label.take() {
if let Some(last) = self.config.series_labels.last_mut() {
if last.is_empty() {
*last = label;
}
}
}
let all_values = match &self.input {
PlotInput::Single(v) => v.clone(),
_ => vec![],
};
let mut series_data: Vec<Vec<f64>> = vec![];
let mut current_series: Vec<f64> = vec![];
for &v in &all_values {
if v.is_nan() {
if !current_series.is_empty() {
series_data.push(current_series);
current_series = vec![];
}
} else {
current_series.push(v);
}
}
if !current_series.is_empty() {
series_data.push(current_series);
}
let series_labels = if self.config.series_labels.is_empty() {
None
} else {
Some(self.config.series_labels.as_slice())
};
let radar_data = crate::plots::compute_radar_chart_with_labels(
&series_data,
&self.config,
series_labels,
);
self.plot.add_radar_series(radar_data, self.style)
}
}
impl_terminal_methods!(crate::plots::RadarConfig);
impl PlotBuilder<crate::plots::PolarPlotConfig> {
pub fn fill(mut self, fill: bool) -> Self {
self.config.fill = fill;
self
}
pub fn fill_alpha(mut self, alpha: f32) -> Self {
self.config.fill_alpha = alpha.clamp(0.0, 1.0);
self
}
pub fn marker_size(mut self, size: f32) -> Self {
self.config.marker_size = size.max(0.0);
self
}
pub fn show_theta_labels(mut self, show: bool) -> Self {
self.config.show_theta_labels = show;
self
}
pub fn show_r_labels(mut self, show: bool) -> Self {
self.config.show_r_labels = show;
self
}
pub fn theta_offset(mut self, offset: f64) -> Self {
self.config.theta_offset = offset;
self
}
fn finalize(self) -> super::Plot {
let (r, theta) = match &self.input {
PlotInput::XY(r, theta) => (r.clone(), theta.clone()),
_ => (vec![], vec![]),
};
let polar_data = crate::plots::compute_polar_plot(&r, &theta, &self.config);
self.plot.add_polar_series(polar_data, self.style)
}
}
impl_terminal_methods!(crate::plots::PolarPlotConfig);
impl PlotBuilder<crate::plots::ViolinConfig> {
pub fn show_box(mut self, show: bool) -> Self {
self.config.show_box = show;
self
}
pub fn show_quartiles(mut self, show: bool) -> Self {
self.config.show_quartiles = show;
self
}
pub fn show_median(mut self, show: bool) -> Self {
self.config.show_median = show;
self
}
pub fn show_points(mut self, show: bool) -> Self {
self.config.show_points = show;
self
}
pub fn split(mut self, split: bool) -> Self {
self.config.split = split;
self
}
pub fn fill_alpha(mut self, alpha: f32) -> Self {
self.config.fill_alpha = alpha.clamp(0.0, 1.0);
self
}
pub fn width(mut self, width: f64) -> Self {
self.config.width = width.max(0.1);
self
}
pub fn horizontal(mut self) -> Self {
self.config.orientation = crate::plots::distribution::violin::Orientation::Horizontal;
self
}
pub fn vertical(mut self) -> Self {
self.config.orientation = crate::plots::distribution::violin::Orientation::Vertical;
self
}
pub fn n_points(mut self, n: usize) -> Self {
self.config.n_points = n.max(10);
self
}
pub fn category(mut self, name: &str) -> Self {
self.config.category = Some(name.to_string());
self
}
fn finalize(self) -> super::Plot {
let data = match &self.input {
PlotInput::Single(d) => d.clone(),
_ => vec![],
};
let violin_data = crate::plots::ViolinData::from_values(&data, &self.config);
match violin_data {
Some(vdata) => self.plot.add_violin_series(vdata, self.style),
None => self.plot, }
}
}
impl_terminal_methods!(crate::plots::ViolinConfig);
impl PlotBuilder<crate::plots::basic::LineConfig> {
pub fn marker(mut self, style: crate::render::MarkerStyle) -> Self {
self.config.marker = Some(style);
self.config.show_markers = true;
self.style.marker_style = Some(style);
self.style.marker_style_source = None;
self
}
pub fn marker_source<S>(mut self, style: S) -> Self
where
S: Into<ReactiveValue<crate::render::MarkerStyle>>,
{
self.config.show_markers = true;
self.style.set_marker_style_source_value(style.into());
self
}
pub fn marker_size(mut self, size: f32) -> Self {
self.config.marker_size = size.max(0.1);
self.style.marker_size = Some(size.max(0.1));
self.style.marker_size_source = None;
self
}
pub fn marker_size_source<S>(mut self, size: S) -> Self
where
S: Into<ReactiveValue<f32>>,
{
self.style.set_marker_size_source_value(size.into());
self
}
pub fn show_markers(mut self, show: bool) -> Self {
self.config.show_markers = show;
self
}
pub fn draw_line(mut self, draw: bool) -> Self {
self.config.draw_line = draw;
self
}
pub fn style(mut self, line_style: crate::render::LineStyle) -> Self {
self.style.line_style = Some(line_style);
self.style.line_style_source = None;
self
}
pub fn style_source<S>(mut self, line_style: S) -> Self
where
S: Into<ReactiveValue<crate::render::LineStyle>>,
{
self.style.set_line_style_source_value(line_style.into());
self
}
fn finalize(self) -> super::Plot {
let (x_data, y_data) = match &self.input {
PlotInput::XY(x, y) => (PlotData::Static(x.clone()), PlotData::Static(y.clone())),
PlotInput::XYSource(x, y) => (x.clone(), y.clone()),
PlotInput::Single(y) => {
let x: Vec<f64> = (0..y.len()).map(|i| i as f64).collect();
(PlotData::Static(x), PlotData::Static(y.clone()))
}
_ => (PlotData::Static(vec![]), PlotData::Static(vec![])),
};
self.plot
.add_line_series(x_data, y_data, &self.config, self.style)
}
}
impl_terminal_methods!(crate::plots::basic::LineConfig);
impl PlotBuilder<crate::plots::basic::ScatterConfig> {
pub fn marker(mut self, style: crate::render::MarkerStyle) -> Self {
self.config.marker = style;
self.style.marker_style = Some(style);
self.style.marker_style_source = None;
self
}
pub fn marker_source<S>(mut self, style: S) -> Self
where
S: Into<ReactiveValue<crate::render::MarkerStyle>>,
{
self.style.set_marker_style_source_value(style.into());
self
}
pub fn marker_size(mut self, size: f32) -> Self {
self.config.size = size.max(0.1);
self.style.marker_size = Some(size.max(0.1));
self.style.marker_size_source = None;
self
}
pub fn marker_size_source<S>(mut self, size: S) -> Self
where
S: Into<ReactiveValue<f32>>,
{
self.style.set_marker_size_source_value(size.into());
self
}
pub fn edge_width(mut self, width: f32) -> Self {
self.config.edge_width = width.max(0.0);
self
}
pub fn edge_color(mut self, color: Color) -> Self {
self.config.edge_color = Some(color);
self
}
fn finalize(self) -> super::Plot {
let (x_data, y_data) = match &self.input {
PlotInput::XY(x, y) => (PlotData::Static(x.clone()), PlotData::Static(y.clone())),
PlotInput::XYSource(x, y) => (x.clone(), y.clone()),
PlotInput::Single(y) => {
let x: Vec<f64> = (0..y.len()).map(|i| i as f64).collect();
(PlotData::Static(x), PlotData::Static(y.clone()))
}
_ => (PlotData::Static(vec![]), PlotData::Static(vec![])),
};
self.plot
.add_scatter_series(x_data, y_data, &self.config, self.style)
}
}
impl_terminal_methods!(crate::plots::basic::ScatterConfig);
impl PlotBuilder<crate::plots::basic::BarConfig> {
pub fn bar_width(mut self, width: f32) -> Self {
self.config.width = width.clamp(0.0, 1.0);
self
}
pub fn edge_width(mut self, width: f32) -> Self {
self.config.edge_width = width.max(0.0);
self
}
pub fn edge_color(mut self, color: Color) -> Self {
self.config.edge_color = Some(color);
self
}
pub fn orientation(mut self, orientation: crate::plots::basic::BarOrientation) -> Self {
self.config.orientation = orientation;
self
}
pub fn bottom(mut self, bottom: f64) -> Self {
self.config.bottom = bottom;
self
}
fn finalize(self) -> super::Plot {
let (categories, values) = match &self.input {
PlotInput::Categorical { categories, values } => {
(categories.clone(), PlotData::Static(values.clone()))
}
PlotInput::CategoricalSource { categories, values } => {
(categories.clone(), values.clone())
}
PlotInput::Single(y) => {
let cats: Vec<String> = (0..y.len()).map(|i| i.to_string()).collect();
(cats, PlotData::Static(y.clone()))
}
_ => (vec![], PlotData::Static(vec![])),
};
self.plot
.add_bar_series(categories, values, &self.config, self.style)
}
}
impl_terminal_methods!(crate::plots::basic::BarConfig);
#[cfg(test)]
mod tests;