use crate::data::{IntoSeries, Series};
use crate::mark::Categories;
use crate::render::Color;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[non_exhaustive]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum PointStyle {
#[default]
Dot,
Plus,
Cross,
Asterisk,
Circle,
}
#[derive(Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Points<'a> {
pub(crate) x: Option<Series<'a>>,
pub(crate) y: Series<'a>,
pub(crate) color: Option<Color>,
pub(crate) label: Option<String>,
#[cfg_attr(feature = "serde", serde(default))]
pub(crate) style: PointStyle,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
pub(crate) color_by: Option<Categories>,
}
impl<'a> Points<'a> {
pub fn y(values: impl IntoSeries<'a>) -> Points<'a> {
Points {
x: None,
y: values.into_series(),
color: None,
label: None,
style: PointStyle::Dot,
color_by: None,
}
}
pub fn xy(x: impl IntoSeries<'a>, y: impl IntoSeries<'a>) -> Points<'a> {
let x = x.into_series();
let y = y.into_series();
let points = Points {
x: Some(x),
y,
color: None,
label: None,
style: PointStyle::Dot,
color_by: None,
};
points
.validate()
.expect("Points::xy requires series of equal length");
points
}
#[must_use]
pub fn style(mut self, style: PointStyle) -> Points<'a> {
self.style = style;
self
}
#[must_use]
pub fn color(mut self, color: Color) -> Points<'a> {
self.color = Some(color);
self
}
#[must_use]
pub fn label(mut self, label: impl Into<String>) -> Points<'a> {
self.label = Some(label.into());
self
}
#[must_use]
pub fn color_by(
mut self,
categories: impl IntoIterator<Item = impl Into<String>>,
) -> Points<'a> {
self.color_by = Some(Categories::new(categories));
self.validate()
.expect("Points::color_by requires one category per point");
self
}
pub(crate) fn validate(&self) -> crate::Result<()> {
if let Some(x) = &self.x {
super::pair("Points: x and y", x.len(), self.y.len())?;
}
if let Some(categories) = &self.color_by {
super::pair("Points: color_by and y", categories.len(), self.y.len())?;
}
Ok(())
}
pub fn into_owned(self) -> Points<'static> {
Points {
x: self.x.map(Series::into_owned),
y: self.y.into_owned(),
color: self.color,
label: self.label,
style: self.style,
color_by: self.color_by,
}
}
}
impl std::fmt::Debug for Points<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Points")
.field("points", &self.y.len())
.field("indexed", &self.x.is_none())
.field("color", &self.color)
.field("style", &self.style)
.finish()
}
}
#[cfg(test)]
#[path = "tests/points_tests.rs"]
mod tests;