use core::fmt;
use iced::Rectangle;
use crate::{Color, camera::Camera, point::MarkerType};
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LineStyle {
Solid,
Dotted { spacing: f32 },
Dashed { length: f32 },
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum MarkerSize {
Pixels(f32),
World(f64),
}
impl From<f32> for MarkerSize {
fn from(size: f32) -> Self {
Self::Pixels(size)
}
}
impl MarkerSize {
pub(crate) fn to_raw(self) -> (f32, u32) {
match self {
Self::Pixels(size) => (size, 0),
Self::World(size) => (size as f32, 1),
}
}
pub(crate) fn marker_size_px(
size: f32,
size_mode: u32,
camera: &Camera,
bounds: &Rectangle,
) -> f32 {
if size_mode != crate::point::MARKER_SIZE_WORLD {
return size;
}
let width = bounds.width.max(1.0) as f64;
let height = bounds.height.max(1.0) as f64;
let world_per_px_x = (2.0 * camera.half_extents.x) / width;
let world_per_px_y = (2.0 * camera.half_extents.y) / height;
let world_per_px_x = world_per_px_x.max(1e-12);
let world_per_px_y = world_per_px_y.max(1e-12);
let px_x = size as f64 / world_per_px_x;
let px_y = size as f64 / world_per_px_y;
px_x.max(px_y) as f32
}
pub(crate) fn to_px(self, camera: &Camera, bounds: &Rectangle) -> f32 {
let (size, size_mode) = self.to_raw();
Self::marker_size_px(size, size_mode, camera, bounds)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct MarkerStyle {
pub size: MarkerSize,
pub marker_type: MarkerType,
}
impl Default for MarkerStyle {
fn default() -> Self {
Self {
size: MarkerSize::Pixels(5.0),
marker_type: MarkerType::FilledCircle,
}
}
}
impl MarkerStyle {
pub fn new(size: f32, marker_type: MarkerType) -> Self {
Self {
size: MarkerSize::Pixels(size),
marker_type,
}
}
pub fn new_world(size: f64, marker_type: MarkerType) -> Self {
Self {
size: MarkerSize::World(size),
marker_type,
}
}
pub fn circle(size: f32) -> Self {
Self {
size: MarkerSize::Pixels(size),
marker_type: MarkerType::FilledCircle,
}
}
pub fn ring(size: f32) -> Self {
Self {
size: MarkerSize::Pixels(size),
marker_type: MarkerType::EmptyCircle,
}
}
pub fn square(size: f32) -> Self {
Self {
size: MarkerSize::Pixels(size),
marker_type: MarkerType::Square,
}
}
pub fn star(size: f32) -> Self {
Self {
size: MarkerSize::Pixels(size),
marker_type: MarkerType::Star,
}
}
pub fn triangle(size: f32) -> Self {
Self {
size: MarkerSize::Pixels(size),
marker_type: MarkerType::Triangle,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum SeriesError {
Empty,
NoMarkersAndNoLines,
NotFound(ShapeId),
InvalidAxisLimits,
InvalidAxisScale,
InvalidPointColorsLength,
InvalidFillEndpoints,
FillEndpointNotFound(ShapeId),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ShapeId(pub(crate) u64);
impl ShapeId {
pub(crate) fn new() -> Self {
use std::sync::atomic::{AtomicU64, Ordering};
static NEXT_ID: AtomicU64 = AtomicU64::new(0);
Self(NEXT_ID.fetch_add(1, Ordering::Relaxed))
}
}
impl fmt::Display for ShapeId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Shape({})", self.0)
}
}
#[derive(Debug, Clone)]
pub struct Series {
pub id: ShapeId,
pub positions: Vec<[f64; 2]>,
pub point_colors: Option<Vec<Color>>,
pub label: Option<String>,
pub color: Color,
pub marker_style: Option<MarkerStyle>,
pub line_style: Option<LineStyle>,
}
impl Series {
pub fn new(positions: Vec<[f64; 2]>, marker_style: MarkerStyle, line_style: LineStyle) -> Self {
Self {
id: ShapeId::new(),
positions,
point_colors: None,
label: None,
color: Color::from_rgb(0.3, 0.3, 0.9),
marker_style: Some(marker_style),
line_style: Some(line_style),
}
}
pub fn line_only(positions: Vec<[f64; 2]>, line_style: LineStyle) -> Self {
Self {
id: ShapeId::new(),
positions,
point_colors: None,
label: None,
color: Color::from_rgb(0.3, 0.3, 0.9),
marker_style: None,
line_style: Some(line_style),
}
}
pub fn markers_only(positions: Vec<[f64; 2]>, marker_style: MarkerStyle) -> Self {
Self {
id: ShapeId::new(),
positions,
point_colors: None,
label: None,
color: Color::from_rgb(0.3, 0.3, 0.9),
marker_style: Some(marker_style),
line_style: None,
}
}
pub fn circles(positions: Vec<[f64; 2]>, size: f32) -> Self {
Self::markers_only(positions, MarkerStyle::circle(size))
}
pub fn squares(positions: Vec<[f64; 2]>, size: f32) -> Self {
Self::markers_only(positions, MarkerStyle::square(size))
}
pub fn stars(positions: Vec<[f64; 2]>, size: f32) -> Self {
Self::markers_only(positions, MarkerStyle::star(size))
}
pub fn triangles(positions: Vec<[f64; 2]>, size: f32) -> Self {
Self::markers_only(positions, MarkerStyle::triangle(size))
}
pub fn with_label(mut self, label: impl Into<String>) -> Self {
let l = label.into();
if !l.is_empty() {
self.label = Some(l);
}
self
}
pub fn with_marker_style(mut self, style: MarkerStyle) -> Self {
self.marker_style = Some(style);
self
}
pub fn with_color(mut self, color: impl Into<Color>) -> Self {
self.color = color.into();
self
}
pub fn with_point_colors(mut self, colors: Vec<Color>) -> Self {
self.point_colors = Some(colors);
self
}
pub fn line_style(mut self, style: LineStyle) -> Self {
self.line_style = Some(style);
self
}
pub fn line_solid(self) -> Self {
self.line_style(LineStyle::Solid)
}
pub fn line_dotted(self, spacing: f32) -> Self {
self.line_style(LineStyle::Dotted { spacing })
}
pub fn line_dashed(self, length: f32) -> Self {
self.line_style(LineStyle::Dashed { length })
}
pub(super) fn validate(&self) -> Result<(), SeriesError> {
if self.positions.is_empty() {
return Err(SeriesError::Empty);
}
if self.marker_style.is_none() && self.line_style.is_none() {
return Err(SeriesError::NoMarkersAndNoLines);
}
if let Some(colors) = &self.point_colors
&& colors.len() != self.positions.len()
{
return Err(SeriesError::InvalidPointColorsLength);
}
Ok(())
}
}