use super::data::{PlotData, ReactiveValue};
use super::types::SeriesStyleProps;
use crate::render::{Color, LineStyle, MarkerStyle};
pub trait BuilderWhen: Sized {
fn when(self, condition: bool, f: impl FnOnce(Self) -> Self) -> Self {
if condition { f(self) } else { self }
}
}
impl BuilderWhen for super::Plot {}
impl<C> BuilderWhen for PlotBuilder<C> where C: crate::plots::PlotConfig + Clone {}
impl BuilderWhen for super::series_builders::SeriesGroupBuilder {}
impl BuilderWhen for crate::core::subplot::SubplotFigure {}
impl BuilderWhen for crate::core::config::PlotConfigBuilder {}
impl BuilderWhen for crate::render::theme::ThemeBuilder {}
#[cfg(all(feature = "interactive", not(target_arch = "wasm32")))]
impl BuilderWhen for crate::interactive::window::InteractiveWindowBuilder {}
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(all(feature = "pdf", not(target_arch = "wasm32")))]
pub fn save_pdf_with_size<P: AsRef<std::path::Path>>(
self,
path: P,
size: Option<(f64, f64)>,
) -> crate::core::Result<()> {
self.finalize().save_pdf_with_size(path, size)
}
#[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());
#[deprecated(
since = "0.1.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
}
}
)+
};
}
macro_rules! impl_colorbar_builder_methods {
($(($config:ty, $series_name:literal, $reads:literal)),+ $(,)?) => {
$(
impl PlotBuilder<$config> {
#[doc = concat!("Show or hide the colorbar. It is on by default, because ", $reads, ".")]
pub fn colorbar(mut self, show: bool) -> Self {
self.config.colorbar = show;
self
}
#[doc = concat!(
"Caption for the colorbar — what this ",
$series_name,
"'s colours are measuring."
)]
pub fn colorbar_label<S: Into<String>>(mut self, label: S) -> Self {
self.config.colorbar_label = Some(label.into());
self
}
pub fn colorbar_tick_font_size(mut self, size: f32) -> Self {
self.config.colorbar_tick_font_size = Some(size.max(1.0));
self
}
pub fn colorbar_label_font_size(mut self, size: f32) -> Self {
self.config.colorbar_label_font_size = Some(size.max(1.0));
self
}
}
)+
};
}
impl_colorbar_builder_methods!(
(
crate::plots::heatmap::HeatmapConfig,
"heatmap",
"the colour of a cell is the whole reading and nothing else decodes it"
),
(
crate::plots::ContourConfig,
"contour",
"the colour of a band is the whole reading and nothing else decodes it"
),
(
crate::plots::continuous::hexbin::HexbinConfig,
"hexbin",
"the colour of a hexagon is the whole reading and nothing else decodes it"
),
(
crate::plots::QuiverConfig,
"quiver",
"an arrow's colour is the only thing that reports its magnitude"
),
);
#[derive(Clone, Debug)]
pub enum PlotInput {
Single(Vec<f64>),
SingleSource(super::PlotData),
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,
},
Quiver {
x: Vec<f64>,
y: Vec<f64>,
u: Vec<f64>,
v: Vec<f64>,
},
ErrorBars {
x: super::PlotData,
y: super::PlotData,
x_errors: Option<super::PlotData>,
y_errors: Option<super::PlotData>,
},
Streaming(crate::data::StreamingXY),
Linkage(crate::stats::clustering::Linkage),
MultiSeries(super::types::MultiSeriesInput),
}
impl PlotInput {
pub fn point_count(&self) -> usize {
match self {
PlotInput::Single(data) => data.len(),
PlotInput::SingleSource(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(),
PlotInput::Quiver { x, .. } => x.len(),
PlotInput::ErrorBars { x, .. } => x.len(),
PlotInput::Streaming(stream) => stream.len(),
PlotInput::MultiSeries(input) => input.point_count(),
PlotInput::Linkage(linkage) => linkage.matrix.len(),
}
}
}
fn resolve_xy_input(input: &PlotInput, style: &mut SeriesStyle) -> (PlotData, PlotData) {
match 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()))
}
PlotInput::Streaming(stream) => {
style.streaming_source = Some(stream.clone());
(
PlotData::Streaming(stream.x().clone()),
PlotData::Streaming(stream.y().clone()),
)
}
_ => (PlotData::Static(vec![]), PlotData::Static(vec![])),
}
}
fn quiver_length_mismatch(
x: &[f64],
y: &[f64],
u: &[f64],
v: &[f64],
) -> Option<crate::core::PlottingError> {
[y.len(), u.len(), v.len()]
.into_iter()
.find(|&len| len != x.len())
.map(|len| crate::core::PlottingError::DataLengthMismatch {
x_len: x.len(),
y_len: len,
series_index: None,
})
}
fn validate_quiver_input(x: &[f64], y: &[f64], u: &[f64], v: &[f64]) -> crate::core::Result<()> {
if let Some(err) = quiver_length_mismatch(x, y, u, v) {
return Err(err);
}
crate::core::PlottingError::validate_data(x)?;
crate::core::PlottingError::validate_data(y)?;
crate::core::PlottingError::validate_data(u)?;
crate::core::PlottingError::validate_data(v)?;
Ok(())
}
#[derive(Clone, Debug, Default)]
pub struct SeriesStyle {
pub label: Option<String>,
pub(crate) props: SeriesStyleProps,
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>,
pub streaming_source: Option<crate::data::StreamingXY>,
}
#[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.props.color.set(color.into());
self
}
pub fn color_source<S>(mut self, color: S) -> Self
where
S: Into<ReactiveValue<Color>>,
{
self.style.props.color.set(color.into());
self
}
pub fn line_width(mut self, width: f32) -> Self {
self.style.props.line_width.set(width.into());
self
}
pub fn line_width_source<S>(mut self, width: S) -> Self
where
S: Into<ReactiveValue<f32>>,
{
self.style.props.line_width.set(width.into());
self
}
pub fn line_style(mut self, style: LineStyle) -> Self {
self.style.props.line_style.set(style.into());
self
}
pub fn line_style_source<S>(mut self, style: S) -> Self
where
S: Into<ReactiveValue<LineStyle>>,
{
self.style.props.line_style.set(style.into());
self
}
pub fn alpha(mut self, alpha: f32) -> Self {
self.style.props.alpha.set(alpha.into());
self
}
pub fn alpha_source<S>(mut self, alpha: S) -> Self
where
S: Into<ReactiveValue<f32>>,
{
self.style.props.alpha.set(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: impl Into<crate::core::LegendPosition>) -> Self {
self.plot = self.plot.legend(position);
self
}
pub fn legend_position(mut self, position: impl Into<crate::core::LegendPosition>) -> Self {
self.plot = self.plot.legend_position(position.into());
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 font_family<F>(mut self, family: F) -> Self
where
F: Into<crate::render::FontFamily>,
{
self.plot = self.plot.font_family(family);
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 xtick_rotation(mut self, rotation: crate::render::XTickRotation) -> Self {
self.plot = self.plot.xtick_rotation(rotation);
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: impl Into<crate::plots::BandwidthMethod>) -> Self {
self.config.bandwidth = bw.into();
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
}
#[deprecated(
since = "0.6.0",
note = "use `line_width`, the spelling shared by every builder; unlike `kde_line_width` it is honoured by every backend"
)]
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
}
#[deprecated(
since = "0.6.0",
note = "use `line_width`, the spelling shared by every builder; unlike `ecdf_line_width` it is honoured by every backend"
)]
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 cmap(mut self, cmap: impl Into<crate::render::ColorMapSpec>) -> Self {
self.config.cmap = cmap.into().into_name();
self
}
#[deprecated(
since = "0.6.0",
note = "renamed: use `cmap(name)`, which also accepts a `ColorMap` value"
)]
pub fn colormap_name(self, name: &str) -> Self {
self.cmap(name)
}
#[deprecated(
since = "0.6.0",
note = "use `line_width`, the spelling shared by every builder; unlike `contour_line_width` it is honoured by every backend"
)]
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
}
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 label_font_size(mut self, size: f32) -> Self {
self.config.label_font_size = size;
self
}
#[deprecated(
since = "0.6.0",
note = "ambiguous against the plot-wide `Plot::font_size`: use `label_font_size`, which sets the pie slice label size"
)]
pub fn font_size(self, size: f32) -> Self {
self.label_font_size(size)
}
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> = match values.as_slice() {
Some(slice) => slice.to_vec(),
None => (0..values.len())
.filter_map(|i| values.get(i).copied())
.collect(),
};
if let Some(label) = self.style.label.take()
&& let Some(last) = self.config.series_labels.last_mut()
&& 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<S: Into<String>>(mut self, name: S) -> Self {
let name = name.into();
if let Some(last) = self.config.series_labels.last_mut() {
last.clone_from(&name);
}
self.style.label = Some(name);
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> = match values.as_slice() {
Some(slice) => slice.to_vec(),
None => (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 series_color(mut self, color: Color) -> Self {
if let Some(idx) = self.config.current_series_idx
&& let Some(ref mut colors) = self.config.colors
&& let Some(c) = colors.get_mut(idx)
{
*c = color;
}
self
}
#[deprecated(
since = "0.6.0",
note = "renamed for consistency with `series_label`: use `series_color`"
)]
pub fn with_color(self, color: Color) -> Self {
self.series_color(color)
}
pub fn series_fill_alpha(mut self, alpha: f32) -> Self {
if let Some(idx) = self.config.current_series_idx
&& let Some(a) = self.config.per_series_fill_alphas.get_mut(idx)
{
*a = Some(alpha.clamp(0.0, 1.0));
}
self
}
#[deprecated(
since = "0.6.0",
note = "renamed for consistency with `series_label`: use `series_fill_alpha`"
)]
pub fn with_fill_alpha(self, alpha: f32) -> Self {
self.series_fill_alpha(alpha)
}
pub fn series_line_width(mut self, width: f32) -> Self {
if let Some(idx) = self.config.current_series_idx
&& let Some(w) = self.config.per_series_line_widths.get_mut(idx)
{
*w = Some(width.max(0.1));
}
self
}
#[deprecated(
since = "0.6.0",
note = "renamed for consistency with `series_label`: use `series_line_width`"
)]
pub fn with_line_width(self, width: f32) -> Self {
self.series_line_width(width)
}
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()
&& let Some(last) = self.config.series_labels.last_mut()
&& 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
}
pub fn show_rgrid(mut self, show: bool) -> Self {
self.config.show_rgrid = show;
self
}
pub fn show_thetagrid(mut self, show: bool) -> Self {
self.config.show_thetagrid = show;
self
}
pub fn rgrid_count(mut self, count: usize) -> Self {
self.config.rgrid_count = count;
self
}
pub fn thetagrid_count(mut self, count: usize) -> Self {
self.config.thetagrid_count = count;
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 violin_width(mut self, width: f64) -> Self {
self.config.width = width.max(0.1);
self
}
#[deprecated(
since = "0.6.0",
note = "ambiguous: `width` means something different on every plot type. Use `violin_width`, which sets the violin body width in data units"
)]
pub fn width(self, width: f64) -> Self {
self.violin_width(width)
}
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 bandwidth(mut self, method: impl Into<crate::plots::BandwidthMethod>) -> Self {
self.config.bandwidth = method.into();
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::BoxenConfig> {
pub fn k_depth(mut self, k: usize) -> Self {
self.config.k_depth = Some(k.max(1));
self
}
pub fn box_width(mut self, width: f64) -> Self {
self.config.width = width.clamp(0.1, 1.0);
self
}
#[deprecated(
since = "0.6.0",
note = "ambiguous: `width` means something different on every plot type. Use `box_width`, which sets the box width as a fraction of category spacing"
)]
pub fn width(self, width: f64) -> Self {
self.box_width(width)
}
pub fn saturation(mut self, saturation: f32) -> Self {
self.config.saturation = saturation.clamp(0.0, 1.0);
self
}
pub fn show_outliers(mut self, show: bool) -> Self {
self.config.show_outliers = show;
self
}
pub fn outlier_size(mut self, size: f32) -> Self {
self.config.outlier_size = size.max(0.0);
self
}
pub fn edge_width(mut self, width: f32) -> Self {
self.config.line_width = width.max(0.0);
self
}
pub fn horizontal(mut self) -> Self {
self.config.orient = crate::plots::distribution::BoxenOrientation::Horizontal;
self
}
pub fn vertical(mut self) -> Self {
self.config.orient = crate::plots::distribution::BoxenOrientation::Vertical;
self
}
fn finalize(self) -> super::Plot {
let data = match &self.input {
PlotInput::Single(data) => data.clone(),
_ => Vec::new(),
};
let boxen_data = crate::plots::compute_boxen(&data, &self.config);
if boxen_data.boxes.is_empty() {
let mut plot = self.plot;
plot.set_pending_ingestion_error(crate::core::PlottingError::EmptyDataSet);
plot
} else {
self.plot.add_boxen_series(boxen_data, self.style)
}
}
}
impl_terminal_methods!(crate::plots::BoxenConfig);
impl PlotBuilder<crate::plots::QuiverConfig> {
pub fn arrow_scale(mut self, scale: f64) -> Self {
self.config.scale = scale.max(0.0);
self
}
#[deprecated(
since = "0.6.0",
note = "ambiguous against the axis-scale setters `xscale`/`yscale`: use `arrow_scale`, which scales arrow lengths"
)]
pub fn scale(self, scale: f64) -> Self {
self.arrow_scale(scale)
}
pub fn arrow_width(mut self, width: f32) -> Self {
self.config.width = width.max(0.1);
self
}
#[deprecated(
since = "0.6.0",
note = "ambiguous: `width` means something different on every plot type. Use `arrow_width`, which sets the arrow shaft stroke width in points"
)]
pub fn width(self, width: f32) -> Self {
self.arrow_width(width)
}
pub fn arrow_head_length(mut self, headlength: f64) -> Self {
self.config.headlength = headlength.max(0.0);
self
}
#[deprecated(
since = "0.6.0",
note = "renamed for consistency with the rest of the `arrow_*` family: use `arrow_head_length`"
)]
pub fn headlength(self, headlength: f64) -> Self {
self.arrow_head_length(headlength)
}
pub fn arrow_head_width(mut self, headwidth: f64) -> Self {
self.config.headwidth = headwidth.max(0.0);
self
}
#[deprecated(
since = "0.6.0",
note = "renamed for consistency with the rest of the `arrow_*` family: use `arrow_head_width`"
)]
pub fn headwidth(self, headwidth: f64) -> Self {
self.arrow_head_width(headwidth)
}
pub fn angles_mode(mut self, enabled: bool) -> Self {
self.config.angles_mode = enabled;
self
}
pub fn pivot(mut self, pivot: crate::plots::QuiverPivot) -> Self {
self.config.pivot = pivot;
self
}
pub fn color_by_magnitude(mut self, enabled: bool) -> Self {
self.config.color_by_magnitude = enabled;
self
}
pub fn cmap(mut self, cmap: impl Into<crate::render::ColorMapSpec>) -> Self {
self.config.cmap = cmap.into().into_name();
self
}
fn finalize(self) -> super::Plot {
if self.plot.pending_ingestion_error().is_some() {
return self.plot;
}
let (x, y, u, v) = match &self.input {
PlotInput::Quiver { x, y, u, v } => (x.clone(), y.clone(), u.clone(), v.clone()),
_ => return self.plot,
};
if let Err(err) = validate_quiver_input(&x, &y, &u, &v) {
let mut plot = self.plot;
plot.set_pending_ingestion_error(err);
return plot;
}
let quiver_data = crate::plots::compute_quiver(&x, &y, &u, &v, &self.config);
self.plot.add_quiver_series(quiver_data, self.style)
}
}
impl_terminal_methods!(crate::plots::QuiverConfig);
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.props.marker_style.set(style.into());
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.props.marker_style.set(style.into());
self
}
pub fn marker_size(mut self, size: f32) -> Self {
self.config.marker_size = size.max(0.1);
self.style.props.marker_size.set(size.into());
self
}
pub fn marker_size_source<S>(mut self, size: S) -> Self
where
S: Into<ReactiveValue<f32>>,
{
self.style.props.marker_size.set(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
}
#[deprecated(
since = "0.6.0",
note = "ambiguous short alias: use `line_style`, the spelling shared by every builder"
)]
pub fn style(self, line_style: crate::render::LineStyle) -> Self {
self.line_style(line_style)
}
#[deprecated(
since = "0.6.0",
note = "ambiguous short alias: use `line_style_source`, the spelling shared by every builder"
)]
pub fn style_source<S>(self, line_style: S) -> Self
where
S: Into<ReactiveValue<crate::render::LineStyle>>,
{
self.line_style_source(line_style)
}
pub fn marker_edge_color(mut self, color: Color) -> Self {
self.config = std::mem::take(&mut self.config).marker_edge_color(color);
self
}
pub fn marker_edge_width(mut self, width: f32) -> Self {
self.config = std::mem::take(&mut self.config).marker_edge_width(width);
self
}
pub fn show_marker_edge(mut self, show: bool) -> Self {
self.config = std::mem::take(&mut self.config).show_marker_edge(show);
self
}
fn finalize(mut self) -> super::Plot {
let (x_data, y_data) = resolve_xy_input(&self.input, &mut self.style);
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.props.marker_style.set(style.into());
self
}
pub fn marker_source<S>(mut self, style: S) -> Self
where
S: Into<ReactiveValue<crate::render::MarkerStyle>>,
{
self.style.props.marker_style.set(style.into());
self
}
pub fn marker_size(mut self, size: f32) -> Self {
self.config.size = size.max(0.1);
self.style.props.marker_size.set(size.into());
self
}
pub fn marker_size_source<S>(mut self, size: S) -> Self
where
S: Into<ReactiveValue<f32>>,
{
self.style.props.marker_size.set(size.into());
self
}
pub fn edge_width(mut self, width: f32) -> Self {
self.config = std::mem::take(&mut self.config).edge_width(width);
self
}
pub fn edge_color(mut self, color: Color) -> Self {
self.config = std::mem::take(&mut self.config).edge_color(color);
self
}
pub fn show_edge(mut self, show: bool) -> Self {
self.config = std::mem::take(&mut self.config).show_edge(show);
self
}
fn finalize(mut self) -> super::Plot {
let (x_data, y_data) = resolve_xy_input(&self.input, &mut self.style);
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);
impl_terminal_methods!(crate::plots::HistogramConfig);
impl_terminal_methods!(crate::plots::BoxPlotConfig);
impl_terminal_methods!(crate::plots::heatmap::HeatmapConfig);
impl_terminal_methods!(crate::plots::error::ErrorBarConfig);
impl_terminal_methods!(crate::plots::distribution::RugConfig);
impl_terminal_methods!(crate::plots::categorical::StripConfig);
impl_terminal_methods!(crate::plots::categorical::SwarmConfig);
impl_terminal_methods!(crate::plots::continuous::hexbin::HexbinConfig);
impl_terminal_methods!(crate::plots::hierarchical::DendrogramConfig);
impl_terminal_methods!(crate::plots::categorical::GroupedBarConfig);
impl_terminal_methods!(crate::plots::categorical::StackedBarConfig);
impl_terminal_methods!(crate::plots::continuous::StackPlotConfig);
#[cfg(test)]
mod tests;
#[cfg(test)]
mod api_consistency_tests {
use super::*;
use crate::core::LegendPosition;
fn sample() -> Vec<f64> {
(0..64)
.map(|i| (i as f64 * 0.37).sin() + i as f64 / 32.0)
.collect()
}
#[test]
fn legend_position_returns_the_builder_so_the_chain_continues() {
let builder = super::super::Plot::new()
.line(&[0.0, 1.0, 2.0], &[0.0, 1.0, 0.5])
.legend_position(LegendPosition::UpperRight)
.line_width(2.5)
.label("series");
assert_eq!(builder.style.props.line_width.cloned(), Some(2.5));
assert_eq!(builder.style.label.as_deref(), Some("series"));
assert!(builder.get_plot().layout.legend.enabled);
assert_eq!(
builder.get_plot().layout.legend.position,
LegendPosition::UpperRight
);
}
#[test]
fn legend_position_is_available_on_non_cartesian_builders_too() {
let builder = super::super::Plot::new()
.pie(&[1.0, 2.0, 3.0])
.legend_position(LegendPosition::OutsideRight)
.donut(0.4);
assert!(builder.get_plot().layout.legend.enabled);
assert_eq!(
builder.get_plot().layout.legend.position,
LegendPosition::OutsideRight
);
assert!((builder.get_config().inner_radius - 0.4).abs() < 1e-12);
}
#[test]
fn legend_legend_best_and_legend_position_agree_on_the_builder() {
let via_legend = super::super::Plot::new()
.kde(&sample())
.legend(LegendPosition::Best);
let via_best = super::super::Plot::new().kde(&sample()).legend_best();
let via_position = super::super::Plot::new()
.kde(&sample())
.legend_position(LegendPosition::Best);
for builder in [&via_legend, &via_best, &via_position] {
assert!(builder.get_plot().layout.legend.enabled);
assert_eq!(
builder.get_plot().layout.legend.position,
LegendPosition::Best
);
}
}
#[test]
fn violin_width_sets_the_body_width_and_the_deprecated_alias_matches() {
let renamed = super::super::Plot::new()
.violin(&sample())
.violin_width(0.75);
#[allow(deprecated)]
let legacy = super::super::Plot::new().violin(&sample()).width(0.75);
assert!((renamed.get_config().width - 0.75).abs() < 1e-12);
assert!((legacy.get_config().width - renamed.get_config().width).abs() < 1e-12);
}
#[test]
fn box_width_sets_the_category_fraction_and_the_deprecated_alias_matches() {
let renamed = super::super::Plot::new().boxen(&sample()).box_width(0.7);
#[allow(deprecated)]
let legacy = super::super::Plot::new().boxen(&sample()).width(0.7);
assert!((renamed.get_config().width - 0.7).abs() < 1e-12);
assert!((legacy.get_config().width - renamed.get_config().width).abs() < 1e-12);
let clamped = super::super::Plot::new().boxen(&sample()).box_width(9.0);
assert!((clamped.get_config().width - 1.0).abs() < 1e-12);
}
#[test]
fn arrow_family_sets_quiver_config_and_the_deprecated_aliases_match() {
let x = vec![0.0, 1.0];
let y = vec![0.0, 1.0];
let u = vec![1.0, 0.5];
let v = vec![0.25, 0.75];
let renamed = super::super::Plot::new()
.quiver(&x, &y, &u, &v)
.arrow_scale(0.25)
.arrow_width(1.25)
.arrow_head_length(0.35)
.arrow_head_width(0.2);
#[allow(deprecated)]
let legacy = super::super::Plot::new()
.quiver(&x, &y, &u, &v)
.scale(0.25)
.width(1.25)
.headlength(0.35)
.headwidth(0.2);
assert!((renamed.get_config().scale - 0.25).abs() < 1e-12);
assert!((renamed.get_config().width - 1.25).abs() < 1e-6);
assert!((renamed.get_config().headlength - 0.35).abs() < 1e-12);
assert!((renamed.get_config().headwidth - 0.2).abs() < 1e-12);
assert!((legacy.get_config().scale - renamed.get_config().scale).abs() < 1e-12);
assert!((legacy.get_config().width - renamed.get_config().width).abs() < 1e-6);
assert!((legacy.get_config().headlength - renamed.get_config().headlength).abs() < 1e-12);
assert!((legacy.get_config().headwidth - renamed.get_config().headwidth).abs() < 1e-12);
}
#[test]
fn line_style_and_the_deprecated_style_alias_are_equivalent() {
let renamed = super::super::Plot::new()
.line(&[0.0, 1.0], &[0.0, 1.0])
.line_style(LineStyle::Dashed);
#[allow(deprecated)]
let legacy = super::super::Plot::new()
.line(&[0.0, 1.0], &[0.0, 1.0])
.style(LineStyle::Dashed);
assert_eq!(
renamed.style.props.line_style.cloned(),
Some(LineStyle::Dashed)
);
assert_eq!(
legacy.style.props.line_style.cloned(),
renamed.style.props.line_style.cloned()
);
assert!(legacy.style.props.line_style.source().is_none());
}
#[test]
fn line_style_source_and_the_deprecated_style_source_alias_are_equivalent() {
let renamed = super::super::Plot::new()
.line(&[0.0, 1.0], &[0.0, 1.0])
.line_style_source(LineStyle::Dotted);
#[allow(deprecated)]
let legacy = super::super::Plot::new()
.line(&[0.0, 1.0], &[0.0, 1.0])
.style_source(LineStyle::Dotted);
assert_eq!(
renamed.style.props.line_style.cloned(),
Some(LineStyle::Dotted)
);
assert_eq!(
legacy.style.props.line_style.cloned(),
renamed.style.props.line_style.cloned()
);
}
#[test]
fn generic_line_width_reaches_every_builder_that_had_a_bespoke_spelling() {
let kde = super::super::Plot::new().kde(&sample()).line_width(3.0);
let ecdf = super::super::Plot::new().ecdf(&sample()).line_width(3.0);
let contour_x = vec![0.0, 1.0, 2.0];
let contour_y = vec![0.0, 1.0];
let contour_z = vec![0.5; contour_x.len() * contour_y.len()];
let contour = super::super::Plot::new()
.contour(&contour_x, &contour_y, &contour_z)
.line_width(3.0);
assert_eq!(kde.style.props.line_width.cloned(), Some(3.0));
assert_eq!(ecdf.style.props.line_width.cloned(), Some(3.0));
assert_eq!(contour.style.props.line_width.cloned(), Some(3.0));
}
#[test]
fn pie_label_font_size_replaces_the_ambiguous_font_size() {
let renamed = super::super::Plot::new()
.pie(&[1.0, 2.0])
.label_font_size(14.0);
#[allow(deprecated)]
let legacy = super::super::Plot::new().pie(&[1.0, 2.0]).font_size(14.0);
assert!((renamed.get_config().label_font_size - 14.0).abs() < 1e-6);
assert!(
(legacy.get_config().label_font_size - renamed.get_config().label_font_size).abs()
< 1e-6
);
}
#[test]
fn radar_series_stylers_replace_the_with_prefixed_spellings() {
let renamed = super::super::Plot::new()
.radar(&["A", "B", "C"])
.add_series("One", &[1.0, 2.0, 3.0])
.series_color(Color::RED)
.series_fill_alpha(0.25)
.series_line_width(3.0);
#[allow(deprecated)]
let legacy = super::super::Plot::new()
.radar(&["A", "B", "C"])
.add_series("One", &[1.0, 2.0, 3.0])
.with_color(Color::RED)
.with_fill_alpha(0.25)
.with_line_width(3.0);
assert_eq!(
renamed.get_config().colors.as_deref(),
Some([Color::RED].as_slice())
);
assert_eq!(
renamed.get_config().per_series_fill_alphas,
vec![Some(0.25)]
);
assert_eq!(renamed.get_config().per_series_line_widths, vec![Some(3.0)]);
assert_eq!(legacy.get_config().colors, renamed.get_config().colors);
assert_eq!(
legacy.get_config().per_series_fill_alphas,
renamed.get_config().per_series_fill_alphas
);
assert_eq!(
legacy.get_config().per_series_line_widths,
renamed.get_config().per_series_line_widths
);
let chart_wide = super::super::Plot::new()
.radar(&["A", "B", "C"])
.add_series("One", &[1.0, 2.0, 3.0])
.fill_alpha(0.9);
assert!((chart_wide.get_config().fill_alpha - 0.9).abs() < 1e-6);
assert_eq!(chart_wide.get_config().per_series_fill_alphas, vec![None]);
}
}