use crate::data::{IntoSeries, Series};
use crate::mark::Categories;
use crate::render::Color;
#[derive(Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
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>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
pub(crate) color_by: Option<Categories>,
}
#[derive(Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
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();
let range = Range {
placement: RangePlacement::Numeric(Some(x)),
low,
high,
body: None,
marker: None,
color: None,
label: None,
color_by: None,
};
range
.validate()
.expect("Range::xy requires series of equal length");
range
}
pub fn y(low: impl IntoSeries<'a>, high: impl IntoSeries<'a>) -> Range<'a> {
let low = low.into_series();
let high = high.into_series();
let range = Range {
placement: RangePlacement::Numeric(None),
low,
high,
body: None,
marker: None,
color: None,
label: None,
color_by: None,
};
range
.validate()
.expect("Range::y requires series of equal length");
range
}
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();
let range = Range {
placement: RangePlacement::Bands(categories),
low,
high,
body: None,
marker: None,
color: None,
label: None,
color_by: None,
};
range
.validate()
.expect("Range::over requires one category per interval");
range
}
#[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();
self.body = Some((low, high));
self.validate()
.expect("Range::body requires series matching the range length");
self
}
#[must_use]
pub fn marker(mut self, values: impl IntoSeries<'a>) -> Range<'a> {
let values = values.into_series();
self.marker = Some(values);
self.validate()
.expect("Range::marker requires a series matching the range length");
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
}
#[must_use]
pub fn color_by(
mut self,
categories: impl IntoIterator<Item = impl Into<String>>,
) -> Range<'a> {
self.color_by = Some(Categories::new(categories));
self.validate()
.expect("Range::color_by requires one category per interval");
self
}
pub(crate) fn validate(&self) -> crate::Result<()> {
let count = self.low.len();
super::pair("Range: low and high", count, self.high.len())?;
match &self.placement {
RangePlacement::Numeric(Some(x)) => {
super::pair("Range: x and low", x.len(), count)?;
}
RangePlacement::Bands(categories) => {
super::pair("Range: categories and low", categories.len(), count)?;
}
RangePlacement::Numeric(None) => {}
}
if let Some((low, high)) = &self.body {
super::pair("Range: body low and high", low.len(), high.len())?;
super::pair("Range: body and low", low.len(), count)?;
}
if let Some(marker) = &self.marker {
super::pair("Range: marker and low", marker.len(), count)?;
}
if let Some(categories) = &self.color_by {
super::pair("Range: color_by and low", categories.len(), count)?;
}
Ok(())
}
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_by: self.color_by,
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;