use std::ops::RangeInclusive;
use std::sync::Arc;
use egui::Color32;
use egui::Id;
use egui::Mesh;
use egui::Pos2;
use egui::Shape;
use egui::Stroke;
use egui::Ui;
use crate::axis::PlotTransform;
use crate::bounds::PlotBounds;
use crate::bounds::PlotPoint;
use crate::colors::DEFAULT_FILL_ALPHA;
use crate::data::PlotPoints;
use crate::items::PlotGeometry;
use crate::items::PlotItem;
use crate::items::PlotItemBase;
pub struct FilledArea {
base: PlotItemBase,
lower_line: Vec<PlotPoint>,
upper_line: Vec<PlotPoint>,
fill_color: Color32,
stroke: Option<Stroke>,
}
impl FilledArea {
pub fn new(name: impl Into<String>, xs: &[f64], ys_min: &[f64], ys_max: &[f64]) -> Self {
assert_eq!(xs.len(), ys_min.len(), "xs and ys_min must have the same length");
assert_eq!(xs.len(), ys_max.len(), "xs and ys_max must have the same length");
let lower_line: Vec<PlotPoint> = xs
.iter()
.zip(ys_min.iter())
.map(|(&x, &y)| PlotPoint::new(x, y))
.collect();
let upper_line: Vec<PlotPoint> = xs
.iter()
.zip(ys_max.iter())
.map(|(&x, &y)| PlotPoint::new(x, y))
.collect();
Self {
base: PlotItemBase::new(name.into()),
lower_line,
upper_line,
fill_color: Color32::from_gray(128).linear_multiply(DEFAULT_FILL_ALPHA),
stroke: None,
}
}
#[inline]
pub fn fill_color(mut self, color: impl Into<Color32>) -> Self {
self.fill_color = color.into();
self
}
#[inline]
pub fn stroke(mut self, stroke: impl Into<Stroke>) -> Self {
self.stroke = Some(stroke.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
}
}
impl PlotItem for FilledArea {
fn shapes(&self, _ui: &Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
if self.lower_line.is_empty() {
return;
}
let n = self.lower_line.len();
let mut mesh = Mesh::default();
mesh.reserve_triangles((n - 1) * 2);
mesh.reserve_vertices(n * 2);
for point in &self.upper_line {
let pos = transform.position_from_point(point);
mesh.colored_vertex(pos, self.fill_color);
}
for point in &self.lower_line {
let pos = transform.position_from_point(point);
mesh.colored_vertex(pos, self.fill_color);
}
for i in 0..(n - 1) {
mesh.add_triangle(i as u32, (n + i) as u32, (i + 1) as u32);
mesh.add_triangle((n + i) as u32, (n + i + 1) as u32, (i + 1) as u32);
}
shapes.push(Shape::Mesh(Arc::new(mesh)));
if let Some(stroke) = self.stroke {
let upper_points: Vec<Pos2> = self
.upper_line
.iter()
.map(|point| transform.position_from_point(point))
.collect();
shapes.push(Shape::line(upper_points, stroke));
let lower_points: Vec<Pos2> = self
.lower_line
.iter()
.map(|point| transform.position_from_point(point))
.collect();
shapes.push(Shape::line(lower_points, stroke));
}
}
fn initialize(&mut self, _x_range: RangeInclusive<f64>) {
}
fn color(&self) -> Color32 {
self.fill_color
}
fn geometry(&self) -> PlotGeometry<'_> {
PlotGeometry::None
}
fn bounds(&self) -> PlotBounds {
let mut all_points = self.lower_line.clone();
all_points.extend(self.upper_line.iter());
PlotPoints::Owned(all_points).bounds()
}
fn base(&self) -> &PlotItemBase {
&self.base
}
fn base_mut(&mut self) -> &mut PlotItemBase {
&mut self.base
}
}