use std::ops::RangeInclusive;
use egui::Color32;
use egui::Id;
use egui::Shape;
use egui::Stroke;
use egui::Ui;
use egui::epaint::CircleShape;
use emath::Pos2;
use emath::pos2;
use emath::vec2;
use crate::aesthetics::MarkerShape;
use crate::axis::PlotTransform;
use crate::bounds::PlotBounds;
use crate::bounds::PlotPoint;
use crate::data::PlotPoints;
use crate::items::PlotGeometry;
use crate::items::PlotItem;
use crate::items::PlotItemBase;
impl<'a> Points<'a> {
pub fn new(name: impl Into<String>, series: impl Into<PlotPoints<'a>>) -> Self {
Self {
base: PlotItemBase::new(name.into()),
series: series.into(),
shape: MarkerShape::Circle,
color: Color32::TRANSPARENT,
filled: true,
radius: 1.0,
stems: None,
}
}
#[inline]
pub fn shape(mut self, shape: MarkerShape) -> Self {
self.shape = shape;
self
}
#[inline]
pub fn color(mut self, color: impl Into<Color32>) -> Self {
self.color = color.into();
self
}
#[inline]
pub fn filled(mut self, filled: bool) -> Self {
self.filled = filled;
self
}
#[inline]
pub fn stems(mut self, y_reference: impl Into<f32>) -> Self {
self.stems = Some(y_reference.into());
self
}
#[inline]
pub fn radius(mut self, radius: impl Into<f32>) -> Self {
self.radius = radius.into();
self
}
#[expect(clippy::needless_pass_by_value, reason = "to allow various string types")]
#[inline]
pub fn name(mut self, name: impl ToString) -> Self {
self.base_mut().name = name.to_string();
self
}
#[inline]
pub fn highlight(mut self, highlight: bool) -> Self {
self.base_mut().highlight = highlight;
self
}
#[inline]
pub fn allow_hover(mut self, hovering: bool) -> Self {
self.base_mut().allow_hover = hovering;
self
}
#[inline]
pub fn id(mut self, id: impl Into<Id>) -> Self {
self.base_mut().id = id.into();
self
}
}
pub struct Points<'a> {
base: PlotItemBase,
pub(crate) series: PlotPoints<'a>,
pub(crate) shape: MarkerShape,
pub(crate) color: Color32,
pub(crate) filled: bool,
pub(crate) radius: f32,
pub(crate) stems: Option<f32>,
}
impl PlotItem for Points<'_> {
fn shapes(&self, _ui: &Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
let sqrt_3 = 3_f32.sqrt();
let frac_sqrt_3_2 = 3_f32.sqrt() / 2.0;
let frac_1_sqrt_2 = 1.0 / 2_f32.sqrt();
let Self {
base,
series,
shape,
color,
filled,
radius,
stems,
..
} = self;
let mut radius = *radius;
let stroke_size = radius / 5.0;
let default_stroke = Stroke::new(stroke_size, *color);
let mut stem_stroke = default_stroke;
let (fill, stroke) = if *filled {
(*color, Stroke::NONE)
} else {
(Color32::TRANSPARENT, default_stroke)
};
if base.highlight {
radius *= 2f32.sqrt();
stem_stroke.width *= 2.0;
}
let y_reference = stems.map(|y| transform.position_from_point(&PlotPoint::new(0.0, y)).y);
series
.points()
.iter()
.map(|value| transform.position_from_point(value))
.for_each(|center| {
let tf = |dx: f32, dy: f32| -> Pos2 { center + radius * vec2(dx, dy) };
if let Some(y) = y_reference {
let stem = Shape::line_segment([center, pos2(center.x, y)], stem_stroke);
shapes.push(stem);
}
match shape {
MarkerShape::Circle => {
shapes.push(Shape::Circle(CircleShape {
center,
radius,
fill,
stroke,
}));
}
MarkerShape::Diamond => {
let points = vec![
tf(0.0, 1.0), tf(-1.0, 0.0), tf(0.0, -1.0), tf(1.0, 0.0), ];
shapes.push(Shape::convex_polygon(points, fill, stroke));
}
MarkerShape::Square => {
let points = vec![
tf(-frac_1_sqrt_2, frac_1_sqrt_2),
tf(-frac_1_sqrt_2, -frac_1_sqrt_2),
tf(frac_1_sqrt_2, -frac_1_sqrt_2),
tf(frac_1_sqrt_2, frac_1_sqrt_2),
];
shapes.push(Shape::convex_polygon(points, fill, stroke));
}
MarkerShape::Cross => {
let diagonal1 = [tf(-frac_1_sqrt_2, -frac_1_sqrt_2), tf(frac_1_sqrt_2, frac_1_sqrt_2)];
let diagonal2 = [tf(frac_1_sqrt_2, -frac_1_sqrt_2), tf(-frac_1_sqrt_2, frac_1_sqrt_2)];
shapes.push(Shape::line_segment(diagonal1, default_stroke));
shapes.push(Shape::line_segment(diagonal2, default_stroke));
}
MarkerShape::Plus => {
let horizontal = [tf(-1.0, 0.0), tf(1.0, 0.0)];
let vertical = [tf(0.0, -1.0), tf(0.0, 1.0)];
shapes.push(Shape::line_segment(horizontal, default_stroke));
shapes.push(Shape::line_segment(vertical, default_stroke));
}
MarkerShape::Up => {
let points = vec![tf(0.0, -1.0), tf(0.5 * sqrt_3, 0.5), tf(-0.5 * sqrt_3, 0.5)];
shapes.push(Shape::convex_polygon(points, fill, stroke));
}
MarkerShape::Down => {
let points = vec![tf(0.0, 1.0), tf(-0.5 * sqrt_3, -0.5), tf(0.5 * sqrt_3, -0.5)];
shapes.push(Shape::convex_polygon(points, fill, stroke));
}
MarkerShape::Left => {
let points = vec![tf(-1.0, 0.0), tf(0.5, -0.5 * sqrt_3), tf(0.5, 0.5 * sqrt_3)];
shapes.push(Shape::convex_polygon(points, fill, stroke));
}
MarkerShape::Right => {
let points = vec![tf(1.0, 0.0), tf(-0.5, 0.5 * sqrt_3), tf(-0.5, -0.5 * sqrt_3)];
shapes.push(Shape::convex_polygon(points, fill, stroke));
}
MarkerShape::Asterisk => {
let vertical = [tf(0.0, -1.0), tf(0.0, 1.0)];
let diagonal1 = [tf(-frac_sqrt_3_2, 0.5), tf(frac_sqrt_3_2, -0.5)];
let diagonal2 = [tf(-frac_sqrt_3_2, -0.5), tf(frac_sqrt_3_2, 0.5)];
shapes.push(Shape::line_segment(vertical, default_stroke));
shapes.push(Shape::line_segment(diagonal1, default_stroke));
shapes.push(Shape::line_segment(diagonal2, default_stroke));
}
}
});
}
fn initialize(&mut self, x_range: RangeInclusive<f64>) {
self.series.generate_points(x_range);
}
fn color(&self) -> Color32 {
self.color
}
fn geometry(&self) -> PlotGeometry<'_> {
PlotGeometry::Points(self.series.points())
}
fn bounds(&self) -> PlotBounds {
self.series.bounds()
}
fn base(&self) -> &PlotItemBase {
&self.base
}
fn base_mut(&mut self) -> &mut PlotItemBase {
&mut self.base
}
}