#[derive(Debug, Clone, PartialEq)]
pub enum LineStyle {
Solid,
Dashed,
Dotted,
DashDot,
DashDotDot,
Custom(Vec<f32>),
}
impl LineStyle {
pub fn to_dash_array(&self) -> Option<Vec<f32>> {
match self {
LineStyle::Solid => None,
LineStyle::Dashed => Some(vec![5.0, 5.0]),
LineStyle::Dotted => Some(vec![1.0, 2.0]),
LineStyle::DashDot => Some(vec![8.0, 3.0, 1.0, 3.0]),
LineStyle::DashDotDot => Some(vec![8.0, 3.0, 1.0, 3.0, 1.0, 3.0]),
LineStyle::Custom(pattern) => {
if pattern.is_empty() {
None
} else {
Some(pattern.clone())
}
}
}
}
pub fn custom<I>(pattern: I) -> Self
where
I: IntoIterator<Item = f32>,
{
let pattern: Vec<f32> = pattern
.into_iter()
.map(|x| x.abs()) .filter(|&x| x > 0.0) .collect();
LineStyle::Custom(pattern)
}
pub fn name(&self) -> &'static str {
match self {
LineStyle::Solid => "solid",
LineStyle::Dashed => "dashed",
LineStyle::Dotted => "dotted",
LineStyle::DashDot => "dash-dot",
LineStyle::DashDotDot => "dash-dot-dot",
LineStyle::Custom(_) => "custom",
}
}
pub fn is_solid(&self) -> bool {
matches!(self, LineStyle::Solid)
}
pub fn is_patterned(&self) -> bool {
!self.is_solid()
}
pub fn pattern_length(&self) -> f32 {
match self.to_dash_array() {
None => 0.0, Some(pattern) => pattern.iter().sum(),
}
}
pub fn scaled(&self, factor: f32) -> Self {
if factor <= 0.0 {
return self.clone();
}
match self {
LineStyle::Custom(pattern) => {
LineStyle::Custom(pattern.iter().map(|&x| x * factor).collect())
}
_ => {
if let Some(pattern) = self.to_dash_array() {
LineStyle::Custom(pattern.iter().map(|&x| x * factor).collect())
} else {
self.clone() }
}
}
}
}
impl Default for LineStyle {
fn default() -> Self {
LineStyle::Solid
}
}
impl std::fmt::Display for LineStyle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
LineStyle::Solid => write!(f, "solid"),
LineStyle::Dashed => write!(f, "dashed"),
LineStyle::Dotted => write!(f, "dotted"),
LineStyle::DashDot => write!(f, "dash-dot"),
LineStyle::DashDotDot => write!(f, "dash-dot-dot"),
LineStyle::Custom(pattern) => {
write!(f, "custom(")?;
for (i, &value) in pattern.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{:.1}", value)?;
}
write!(f, ")")
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MarkerStyle {
Circle,
Square,
Triangle,
TriangleDown,
Diamond,
Plus,
Cross,
Star,
CircleOpen,
SquareOpen,
TriangleOpen,
DiamondOpen,
}
impl MarkerStyle {
pub fn name(&self) -> &'static str {
match self {
MarkerStyle::Circle => "circle",
MarkerStyle::Square => "square",
MarkerStyle::Triangle => "triangle",
MarkerStyle::TriangleDown => "triangle-down",
MarkerStyle::Diamond => "diamond",
MarkerStyle::Plus => "plus",
MarkerStyle::Cross => "cross",
MarkerStyle::Star => "star",
MarkerStyle::CircleOpen => "circle-open",
MarkerStyle::SquareOpen => "square-open",
MarkerStyle::TriangleOpen => "triangle-open",
MarkerStyle::DiamondOpen => "diamond-open",
}
}
pub fn is_filled(&self) -> bool {
matches!(
self,
MarkerStyle::Circle
| MarkerStyle::Square
| MarkerStyle::Triangle
| MarkerStyle::TriangleDown
| MarkerStyle::Diamond
| MarkerStyle::Star
)
}
pub fn is_hollow(&self) -> bool {
matches!(
self,
MarkerStyle::CircleOpen
| MarkerStyle::SquareOpen
| MarkerStyle::TriangleOpen
| MarkerStyle::DiamondOpen
)
}
pub fn is_line_based(&self) -> bool {
matches!(self, MarkerStyle::Plus | MarkerStyle::Cross)
}
}
impl Default for MarkerStyle {
fn default() -> Self {
MarkerStyle::Circle
}
}
impl std::fmt::Display for MarkerStyle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_line_style_dash_arrays() {
assert_eq!(LineStyle::Solid.to_dash_array(), None);
assert_eq!(LineStyle::Dashed.to_dash_array(), Some(vec![5.0, 5.0]));
assert_eq!(LineStyle::Dotted.to_dash_array(), Some(vec![1.0, 2.0]));
assert_eq!(
LineStyle::DashDot.to_dash_array(),
Some(vec![8.0, 3.0, 1.0, 3.0])
);
}
#[test]
fn test_custom_line_style() {
let custom = LineStyle::custom(vec![4.0, 2.0, 1.0, 2.0]);
assert_eq!(custom.to_dash_array(), Some(vec![4.0, 2.0, 1.0, 2.0]));
let custom_filtered = LineStyle::custom(vec![4.0, 0.0, -2.0, 3.0]);
assert_eq!(custom_filtered.to_dash_array(), Some(vec![4.0, 2.0, 3.0]));
let empty = LineStyle::custom(Vec::<f32>::new());
assert_eq!(empty.to_dash_array(), None);
}
#[test]
fn test_line_style_properties() {
assert!(LineStyle::Solid.is_solid());
assert!(!LineStyle::Dashed.is_solid());
assert!(LineStyle::Dashed.is_patterned());
assert!(!LineStyle::Solid.is_patterned());
}
#[test]
fn test_pattern_length() {
assert_eq!(LineStyle::Solid.pattern_length(), 0.0);
assert_eq!(LineStyle::Dashed.pattern_length(), 10.0); assert_eq!(LineStyle::DashDot.pattern_length(), 15.0); }
#[test]
fn test_line_style_scaling() {
let scaled_dashed = LineStyle::Dashed.scaled(2.0);
assert_eq!(scaled_dashed.to_dash_array(), Some(vec![10.0, 10.0]));
let scaled_solid = LineStyle::Solid.scaled(2.0);
assert_eq!(scaled_solid, LineStyle::Solid);
let custom = LineStyle::custom(vec![2.0, 1.0]);
let scaled_custom = custom.scaled(3.0);
assert_eq!(scaled_custom.to_dash_array(), Some(vec![6.0, 3.0]));
}
#[test]
fn test_line_style_names() {
assert_eq!(LineStyle::Solid.name(), "solid");
assert_eq!(LineStyle::DashDot.name(), "dash-dot");
assert_eq!(LineStyle::custom(vec![1.0, 2.0]).name(), "custom");
}
#[test]
fn test_marker_style_properties() {
assert!(MarkerStyle::Circle.is_filled());
assert!(!MarkerStyle::CircleOpen.is_filled());
assert!(MarkerStyle::CircleOpen.is_hollow());
assert!(!MarkerStyle::Circle.is_hollow());
assert!(MarkerStyle::Plus.is_line_based());
assert!(!MarkerStyle::Circle.is_line_based());
}
#[test]
fn test_marker_style_names() {
assert_eq!(MarkerStyle::Circle.name(), "circle");
assert_eq!(MarkerStyle::TriangleDown.name(), "triangle-down");
assert_eq!(MarkerStyle::CircleOpen.name(), "circle-open");
}
#[test]
fn test_defaults() {
assert_eq!(LineStyle::default(), LineStyle::Solid);
assert_eq!(MarkerStyle::default(), MarkerStyle::Circle);
}
#[test]
fn test_display() {
assert_eq!(LineStyle::Solid.to_string(), "solid");
assert_eq!(
LineStyle::custom(vec![1.0, 2.5]).to_string(),
"custom(1.0, 2.5)"
);
assert_eq!(MarkerStyle::Circle.to_string(), "circle");
}
}