use std::sync::Arc;
use crate::axis_link::AxisLink;
use crate::axis_scale::AxisScale;
use crate::controls::PlotControls;
use crate::fill::Fill;
use crate::message::TooltipContext;
use crate::plot_widget::{CursorProvider, HighlightPoint, HighlightPointProvider, PlotWidget};
use crate::reference_lines::{HLine, VLine};
use crate::series::{Series, SeriesError};
use crate::ticks::{Tick, TickFormatter, TickProducer};
#[derive(Default)]
pub struct PlotWidgetBuilder {
x_label: Option<String>,
y_label: Option<String>,
autoscale_on_updates: Option<bool>,
hover_radius_px: Option<f32>,
pick_highlight_provider: Option<HighlightPointProvider>,
hover_highlight_provider: Option<HighlightPointProvider>,
cursor_overlay: Option<bool>,
cursor_provider: Option<CursorProvider>,
crosshairs: Option<bool>,
controls: Option<PlotControls>,
disable_legend: bool,
x_lim: Option<(f64, f64)>,
y_lim: Option<(f64, f64)>,
x_axis_scale: Option<AxisScale>,
y_axis_scale: Option<AxisScale>,
x_axis_link: Option<AxisLink>,
y_axis_link: Option<AxisLink>,
x_tick_formatter: Option<TickFormatter>,
y_tick_formatter: Option<TickFormatter>,
x_tick_producer: Option<TickProducer>,
y_tick_producer: Option<TickProducer>,
enable_x_tick_labels: Option<bool>,
enable_y_tick_labels: Option<bool>,
tick_label_size: Option<f32>,
axis_label_size: Option<f32>,
data_aspect: Option<f64>,
series: Vec<Series>,
fills: Vec<Fill>,
vlines: Vec<VLine>,
hlines: Vec<HLine>,
}
impl PlotWidgetBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn with_x_label(mut self, label: impl Into<String>) -> Self {
let l = label.into();
if !l.is_empty() {
self.x_label = Some(l);
}
self
}
pub fn with_y_label(mut self, label: impl Into<String>) -> Self {
let l = label.into();
if !l.is_empty() {
self.y_label = Some(l);
}
self
}
pub fn with_autoscale_on_updates(mut self, enabled: bool) -> Self {
self.autoscale_on_updates = Some(enabled);
self
}
pub fn with_hover_radius_px(mut self, radius: f32) -> Self {
self.hover_radius_px = Some(radius.max(0.0));
self
}
pub fn with_pick_highlight_provider<F>(mut self, provider: F) -> Self
where
F: Fn(TooltipContext<'_>, &mut HighlightPoint) -> Option<String> + Send + Sync + 'static,
{
self.pick_highlight_provider = Some(Arc::new(provider));
self
}
pub fn with_hover_highlight_provider<F>(mut self, provider: F) -> Self
where
F: Fn(TooltipContext<'_>, &mut HighlightPoint) -> Option<String> + Send + Sync + 'static,
{
self.hover_highlight_provider = Some(Arc::new(provider));
self
}
pub fn with_cursor_overlay(mut self, enabled: bool) -> Self {
self.cursor_overlay = Some(enabled);
self
}
pub fn with_cursor_provider<F>(mut self, provider: F) -> Self
where
F: Fn(f64, f64) -> String + Send + Sync + 'static,
{
self.cursor_provider = Some(Arc::new(provider));
self
}
pub fn with_crosshairs(mut self, enabled: bool) -> Self {
self.crosshairs = Some(enabled);
self
}
pub fn disable_controls_help(mut self) -> Self {
self.controls.get_or_insert_default().show_controls_help = false;
self
}
pub fn disable_legend(mut self) -> Self {
self.disable_legend = true;
self
}
pub fn disable_scroll_to_pan(mut self) -> Self {
self.controls.get_or_insert_default().pan.scroll_to_pan = false;
self
}
pub fn with_controls(mut self, controls: PlotControls) -> Self {
self.controls = Some(controls);
self
}
pub fn with_x_lim(mut self, min: f64, max: f64) -> Self {
self.x_lim = Some((min, max));
self
}
pub fn with_x_scale(mut self, scale: AxisScale) -> Self {
self.x_axis_scale = Some(scale);
self
}
pub fn with_y_lim(mut self, min: f64, max: f64) -> Self {
self.y_lim = Some((min, max));
self
}
pub fn with_y_scale(mut self, scale: AxisScale) -> Self {
self.y_axis_scale = Some(scale);
self
}
pub fn with_x_axis_link(mut self, link: AxisLink) -> Self {
self.x_axis_link = Some(link);
self
}
pub fn with_y_axis_link(mut self, link: AxisLink) -> Self {
self.y_axis_link = Some(link);
self
}
pub fn with_x_tick_formatter<F>(mut self, formatter: F) -> Self
where
F: Fn(Tick) -> String + Send + Sync + 'static,
{
self.x_tick_formatter = Some(Arc::new(formatter));
self
}
pub fn with_y_tick_formatter<F>(mut self, formatter: F) -> Self
where
F: Fn(Tick) -> String + Send + Sync + 'static,
{
self.y_tick_formatter = Some(Arc::new(formatter));
self
}
pub fn with_x_tick_producer<F>(mut self, producer: F) -> Self
where
F: Fn(f64, f64) -> Vec<Tick> + Send + Sync + 'static,
{
self.x_tick_producer = Some(Arc::new(producer));
self
}
pub fn with_y_tick_producer<F>(mut self, producer: F) -> Self
where
F: Fn(f64, f64) -> Vec<Tick> + Send + Sync + 'static,
{
self.y_tick_producer = Some(Arc::new(producer));
self
}
pub fn with_x_tick_labels(mut self, enabled: bool) -> Self {
self.enable_x_tick_labels = Some(enabled);
self
}
pub fn with_y_tick_labels(mut self, enabled: bool) -> Self {
self.enable_y_tick_labels = Some(enabled);
self
}
pub fn with_tick_label_size(mut self, size: f32) -> Self {
self.tick_label_size = Some(size.max(1.0));
self
}
pub fn with_axis_label_size(mut self, size: f32) -> Self {
self.axis_label_size = Some(size.max(1.0));
self
}
pub fn with_data_aspect(mut self, aspect: f64) -> Self {
if aspect.is_sign_positive() {
self.data_aspect = Some(aspect);
} else {
self.data_aspect = None;
}
self
}
pub fn add_series(mut self, series: Series) -> Self {
self.series.push(series);
self
}
pub fn add_vline(mut self, vline: VLine) -> Self {
self.vlines.push(vline);
self
}
pub fn add_fill(mut self, fill: Fill) -> Self {
self.fills.push(fill);
self
}
pub fn add_hline(mut self, hline: HLine) -> Self {
self.hlines.push(hline);
self
}
pub fn without_grid(self) -> Self {
self.with_x_tick_producer(|_, _| Vec::new())
.with_y_tick_producer(|_, _| Vec::new())
}
pub fn default_hover_highlight_provider(
ctx: TooltipContext<'_>,
point: &mut HighlightPoint,
) -> Option<String> {
if ctx.series_label.is_empty() {
Some(format!("x: {:.2}, y: {:.2}", point.x, point.y))
} else {
Some(format!(
"{}\nx: {:.2}, y: {:.2}",
ctx.series_label, point.x, point.y
))
}
}
pub fn build(self) -> Result<PlotWidget, SeriesError> {
let x_axis_scale = self.x_axis_scale.unwrap_or_default();
let y_axis_scale = self.y_axis_scale.unwrap_or_default();
for scale in [x_axis_scale, y_axis_scale] {
if let AxisScale::Log { base } = scale
&& !(base.is_finite() && base > 1.0)
{
return Err(SeriesError::InvalidAxisScale);
}
}
if let Some((x_min, x_max)) = self.x_lim
&& x_min >= x_max
{
return Err(SeriesError::InvalidAxisLimits);
}
if let Some((y_min, y_max)) = self.y_lim
&& y_min >= y_max
{
return Err(SeriesError::InvalidAxisLimits);
}
let mut w = PlotWidget::new();
w.set_x_axis_scale(x_axis_scale);
w.set_y_axis_scale(y_axis_scale);
if let Some(controls) = self.controls {
w.set_controls(controls);
}
if self.disable_legend {
w.legend_enabled = false;
}
if let Some(enabled) = self.autoscale_on_updates {
w.autoscale_on_updates(enabled);
}
if let Some(r) = self.hover_radius_px {
w.hover_radius_px(r);
}
if let Some(x) = self.x_label {
w.set_x_axis_label(x);
}
if let Some(y) = self.y_label {
w.set_y_axis_label(y);
}
if let Some((min, max)) = self.x_lim {
w.set_x_lim(min, max);
}
if let Some((min, max)) = self.y_lim {
w.set_y_lim(min, max);
}
if let Some(c) = self.cursor_overlay {
w.set_cursor_overlay(c);
}
if let Some(p) = self.pick_highlight_provider {
w.set_pick_highlight_provider(p);
}
if let Some(p) = self.hover_highlight_provider {
w.set_hover_highlight_provider(p);
} else {
w.set_hover_highlight_provider(Arc::new(Self::default_hover_highlight_provider));
}
if let Some(p) = self.cursor_provider {
w.set_cursor_provider(p);
}
if let Some(enabled) = self.crosshairs {
w.set_crosshairs(enabled);
}
if let Some(link) = self.x_axis_link {
w.set_x_axis_link(link);
}
if let Some(link) = self.y_axis_link {
w.set_y_axis_link(link);
}
if let Some(formatter) = self.x_tick_formatter {
w.set_x_axis_formatter(formatter);
}
if let Some(formatter) = self.y_tick_formatter {
w.set_y_axis_formatter(formatter);
}
if self.enable_x_tick_labels == Some(false) {
w.x_axis_formatter = None;
}
if self.enable_y_tick_labels == Some(false) {
w.y_axis_formatter = None;
}
if let Some(producer) = self.x_tick_producer {
w.set_x_tick_producer(producer);
}
if let Some(producer) = self.y_tick_producer {
w.set_y_tick_producer(producer);
}
if let Some(size) = self.tick_label_size {
w.tick_label_size = size;
}
if let Some(size) = self.axis_label_size {
w.axis_label_size = size;
}
if let Some(aspect) = self.data_aspect {
w.set_data_aspect(aspect);
}
for s in self.series {
w.add_series(s)?;
}
for vline in self.vlines {
w.add_vline(vline);
}
for hline in self.hlines {
w.add_hline(hline);
}
for fill in self.fills {
w.add_fill(fill)?;
}
Ok(w)
}
}