198 lines
5.6 KiB
Rust
198 lines
5.6 KiB
Rust
use egui::emath::NumExt;
|
|
use egui::epaint::{Color32, RectShape, Rounding, Shape, Stroke};
|
|
|
|
use super::{add_rulers_and_text, highlighted_color, Orientation, PlotConfig, RectElement};
|
|
use crate::{BarChart, Cursor, PlotPoint, PlotTransform};
|
|
|
|
/// One bar in a [`BarChart`]. Potentially floating, allowing stacked bar charts.
|
|
/// Width can be changed to allow variable-width histograms.
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub struct Bar {
|
|
/// Name of plot element in the diagram (annotated by default formatter)
|
|
pub name: String,
|
|
|
|
/// Which direction the bar faces in the diagram
|
|
pub orientation: Orientation,
|
|
|
|
/// Position on the argument (input) axis -- X if vertical, Y if horizontal
|
|
pub argument: f64,
|
|
|
|
/// Position on the value (output) axis -- Y if vertical, X if horizontal
|
|
pub value: f64,
|
|
|
|
/// For stacked bars, this denotes where the bar starts. None if base axis
|
|
pub base_offset: Option<f64>,
|
|
|
|
/// Thickness of the bar
|
|
pub bar_width: f64,
|
|
|
|
/// Line width and color
|
|
pub stroke: Stroke,
|
|
|
|
/// Fill color
|
|
pub fill: Color32,
|
|
}
|
|
|
|
impl Bar {
|
|
/// Create a bar. Its `orientation` is set by its [`BarChart`] parent.
|
|
///
|
|
/// - `argument`: Position on the argument axis (X if vertical, Y if horizontal).
|
|
/// - `value`: Height of the bar (if vertical).
|
|
///
|
|
/// By default the bar is vertical and its base is at zero.
|
|
pub fn new(argument: f64, height: f64) -> Self {
|
|
Self {
|
|
argument,
|
|
value: height,
|
|
orientation: Orientation::default(),
|
|
name: Default::default(),
|
|
base_offset: None,
|
|
bar_width: 0.5,
|
|
stroke: Stroke::new(1.0, Color32::TRANSPARENT),
|
|
fill: Color32::TRANSPARENT,
|
|
}
|
|
}
|
|
|
|
/// Name of this bar chart element.
|
|
#[allow(clippy::needless_pass_by_value)]
|
|
#[inline]
|
|
pub fn name(mut self, name: impl ToString) -> Self {
|
|
self.name = name.to_string();
|
|
self
|
|
}
|
|
|
|
/// Add a custom stroke.
|
|
#[inline]
|
|
pub fn stroke(mut self, stroke: impl Into<Stroke>) -> Self {
|
|
self.stroke = stroke.into();
|
|
self
|
|
}
|
|
|
|
/// Add a custom fill color.
|
|
#[inline]
|
|
pub fn fill(mut self, color: impl Into<Color32>) -> Self {
|
|
self.fill = color.into();
|
|
self
|
|
}
|
|
|
|
/// Offset the base of the bar.
|
|
/// This offset is on the Y axis for a vertical bar
|
|
/// and on the X axis for a horizontal bar.
|
|
#[inline]
|
|
pub fn base_offset(mut self, offset: f64) -> Self {
|
|
self.base_offset = Some(offset);
|
|
self
|
|
}
|
|
|
|
/// Set the bar width.
|
|
#[inline]
|
|
pub fn width(mut self, width: f64) -> Self {
|
|
self.bar_width = width;
|
|
self
|
|
}
|
|
|
|
/// Set orientation of the element as vertical. Argument axis is X.
|
|
#[inline]
|
|
pub fn vertical(mut self) -> Self {
|
|
self.orientation = Orientation::Vertical;
|
|
self
|
|
}
|
|
|
|
/// Set orientation of the element as horizontal. Argument axis is Y.
|
|
#[inline]
|
|
pub fn horizontal(mut self) -> Self {
|
|
self.orientation = Orientation::Horizontal;
|
|
self
|
|
}
|
|
|
|
pub(super) fn lower(&self) -> f64 {
|
|
if self.value.is_sign_positive() {
|
|
self.base_offset.unwrap_or(0.0)
|
|
} else {
|
|
self.base_offset.map_or(self.value, |o| o + self.value)
|
|
}
|
|
}
|
|
|
|
pub(super) fn upper(&self) -> f64 {
|
|
if self.value.is_sign_positive() {
|
|
self.base_offset.map_or(self.value, |o| o + self.value)
|
|
} else {
|
|
self.base_offset.unwrap_or(0.0)
|
|
}
|
|
}
|
|
|
|
pub(super) fn add_shapes(
|
|
&self,
|
|
transform: &PlotTransform,
|
|
highlighted: bool,
|
|
shapes: &mut Vec<Shape>,
|
|
) {
|
|
let (stroke, fill) = if highlighted {
|
|
highlighted_color(self.stroke, self.fill)
|
|
} else {
|
|
(self.stroke, self.fill)
|
|
};
|
|
|
|
let rect = transform.rect_from_values(&self.bounds_min(), &self.bounds_max());
|
|
let rect = Shape::Rect(RectShape::new(rect, Rounding::ZERO, fill, stroke));
|
|
|
|
shapes.push(rect);
|
|
}
|
|
|
|
pub(super) fn add_rulers_and_text(
|
|
&self,
|
|
parent: &BarChart,
|
|
plot: &PlotConfig<'_>,
|
|
shapes: &mut Vec<Shape>,
|
|
cursors: &mut Vec<Cursor>,
|
|
) {
|
|
let text: Option<String> = parent
|
|
.element_formatter
|
|
.as_ref()
|
|
.map(|fmt| fmt(self, parent));
|
|
|
|
add_rulers_and_text(self, plot, text, shapes, cursors);
|
|
}
|
|
}
|
|
|
|
impl RectElement for Bar {
|
|
fn name(&self) -> &str {
|
|
self.name.as_str()
|
|
}
|
|
|
|
fn bounds_min(&self) -> PlotPoint {
|
|
self.point_at(self.argument - self.bar_width / 2.0, self.lower())
|
|
}
|
|
|
|
fn bounds_max(&self) -> PlotPoint {
|
|
self.point_at(self.argument + self.bar_width / 2.0, self.upper())
|
|
}
|
|
|
|
fn values_with_ruler(&self) -> Vec<PlotPoint> {
|
|
let base = self.base_offset.unwrap_or(0.0);
|
|
let value_center = self.point_at(self.argument, base + self.value);
|
|
|
|
let mut ruler_positions = vec![value_center];
|
|
|
|
if let Some(offset) = self.base_offset {
|
|
ruler_positions.push(self.point_at(self.argument, offset));
|
|
}
|
|
|
|
ruler_positions
|
|
}
|
|
|
|
fn orientation(&self) -> Orientation {
|
|
self.orientation
|
|
}
|
|
|
|
fn default_values_format(&self, transform: &PlotTransform) -> String {
|
|
let scale = transform.dvalue_dpos();
|
|
let scale = match self.orientation {
|
|
Orientation::Horizontal => scale[0],
|
|
Orientation::Vertical => scale[1],
|
|
};
|
|
let decimals = ((-scale.abs().log10()).ceil().at_least(0.0) as usize).at_most(6);
|
|
crate::format_number(self.value, decimals)
|
|
}
|
|
}
|