use crate::data::{IntoSeries, Series};
use crate::render::Color;
#[derive(Clone)]
pub struct Range<'a> {
pub(crate) placement: RangePlacement<'a>,
pub(crate) low: Series<'a>,
pub(crate) high: Series<'a>,
pub(crate) body: Option<(Series<'a>, Series<'a>)>,
pub(crate) marker: Option<Series<'a>>,
pub(crate) color: Option<Color>,
pub(crate) label: Option<String>,
}
#[derive(Clone)]
pub(crate) enum RangePlacement<'a> {
Numeric(Option<Series<'a>>),
Bands(Vec<String>),
}
impl<'a> Range<'a> {
pub fn xy(
x: impl IntoSeries<'a>,
low: impl IntoSeries<'a>,
high: impl IntoSeries<'a>,
) -> Range<'a> {
let x = x.into_series();
let low = low.into_series();
let high = high.into_series();
assert!(
x.len() == low.len() && low.len() == high.len(),
"Range::xy requires series of equal length"
);
Range {
placement: RangePlacement::Numeric(Some(x)),
low,
high,
body: None,
marker: None,
color: None,
label: None,
}
}
pub fn y(low: impl IntoSeries<'a>, high: impl IntoSeries<'a>) -> Range<'a> {
let low = low.into_series();
let high = high.into_series();
assert_eq!(
low.len(),
high.len(),
"Range::y requires series of equal length"
);
Range {
placement: RangePlacement::Numeric(None),
low,
high,
body: None,
marker: None,
color: None,
label: None,
}
}
pub fn over(
categories: impl IntoIterator<Item = impl Into<String>>,
low: impl IntoSeries<'a>,
high: impl IntoSeries<'a>,
) -> Range<'a> {
let categories: Vec<String> = categories.into_iter().map(Into::into).collect();
let low = low.into_series();
let high = high.into_series();
assert!(
categories.len() == low.len() && low.len() == high.len(),
"Range::over requires one category per interval"
);
Range {
placement: RangePlacement::Bands(categories),
low,
high,
body: None,
marker: None,
color: None,
label: None,
}
}
#[must_use]
pub fn body(mut self, low: impl IntoSeries<'a>, high: impl IntoSeries<'a>) -> Range<'a> {
let low = low.into_series();
let high = high.into_series();
assert!(
low.len() == self.low.len() && high.len() == self.low.len(),
"Range::body requires series matching the range length"
);
self.body = Some((low, high));
self
}
#[must_use]
pub fn marker(mut self, values: impl IntoSeries<'a>) -> Range<'a> {
let values = values.into_series();
assert_eq!(
values.len(),
self.low.len(),
"Range::marker requires a series matching the range length"
);
self.marker = Some(values);
self
}
#[must_use]
pub fn color(mut self, color: Color) -> Range<'a> {
self.color = Some(color);
self
}
#[must_use]
pub fn label(mut self, label: impl Into<String>) -> Range<'a> {
self.label = Some(label.into());
self
}
pub fn into_owned(self) -> Range<'static> {
Range {
placement: match self.placement {
RangePlacement::Numeric(x) => RangePlacement::Numeric(x.map(Series::into_owned)),
RangePlacement::Bands(categories) => RangePlacement::Bands(categories),
},
low: self.low.into_owned(),
high: self.high.into_owned(),
body: self
.body
.map(|(low, high)| (low.into_owned(), high.into_owned())),
marker: self.marker.map(Series::into_owned),
color: self.color,
label: self.label,
}
}
}
impl std::fmt::Debug for Range<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Range")
.field("intervals", &self.low.len())
.field("boxed", &self.body.is_some())
.field("color", &self.color)
.finish()
}
}
#[cfg(test)]
#[path = "tests/range_tests.rs"]
mod tests;