mirror of
https://github.com/emilk/egui.git
synced 2026-08-31 05:40:03 -04:00
Plot: Line styles (#482)
* added new line styles * update changelog * fix #524 Add missing functions to `HLine` and `VLine` * add functions for creating points and dashes from a line * apply suggestions * clippy fix * address comments
This commit is contained in:
committed by
GitHub
parent
d8b2b50780
commit
7c5a2d60c5
@@ -34,6 +34,93 @@ impl Value {
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
pub enum LineStyle {
|
||||
Solid,
|
||||
Dotted { spacing: f32 },
|
||||
Dashed { length: f32 },
|
||||
}
|
||||
|
||||
impl LineStyle {
|
||||
pub fn dashed_loose() -> Self {
|
||||
Self::Dashed { length: 10.0 }
|
||||
}
|
||||
|
||||
pub fn dashed_dense() -> Self {
|
||||
Self::Dashed { length: 5.0 }
|
||||
}
|
||||
|
||||
pub fn dotted_loose() -> Self {
|
||||
Self::Dotted { spacing: 10.0 }
|
||||
}
|
||||
|
||||
pub fn dotted_dense() -> Self {
|
||||
Self::Dotted { spacing: 5.0 }
|
||||
}
|
||||
|
||||
fn style_line(
|
||||
&self,
|
||||
line: Vec<Pos2>,
|
||||
mut stroke: Stroke,
|
||||
highlight: bool,
|
||||
shapes: &mut Vec<Shape>,
|
||||
) {
|
||||
match line.len() {
|
||||
0 => {}
|
||||
1 => {
|
||||
let mut radius = stroke.width / 2.0;
|
||||
if highlight {
|
||||
radius *= 2f32.sqrt();
|
||||
}
|
||||
shapes.push(Shape::circle_filled(line[0], radius, stroke.color));
|
||||
}
|
||||
_ => {
|
||||
match self {
|
||||
LineStyle::Solid => {
|
||||
if highlight {
|
||||
stroke.width *= 2.0;
|
||||
}
|
||||
shapes.push(Shape::line(line, stroke));
|
||||
}
|
||||
LineStyle::Dotted { spacing } => {
|
||||
// Take the stroke width for the radius even though it's not "correct", otherwise
|
||||
// the dots would become too small.
|
||||
let mut radius = stroke.width;
|
||||
if highlight {
|
||||
radius *= 2f32.sqrt();
|
||||
}
|
||||
shapes.extend(Shape::dotted_line(&line, stroke.color, *spacing, radius))
|
||||
}
|
||||
LineStyle::Dashed { length } => {
|
||||
if highlight {
|
||||
stroke.width *= 2.0;
|
||||
}
|
||||
let golden_ratio = (5.0_f32.sqrt() - 1.0) / 2.0; // 0.61803398875
|
||||
shapes.extend(Shape::dashed_line(
|
||||
&line,
|
||||
stroke,
|
||||
*length,
|
||||
length * golden_ratio,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for LineStyle {
|
||||
fn to_string(&self) -> String {
|
||||
match self {
|
||||
LineStyle::Solid => "Solid".into(),
|
||||
LineStyle::Dotted { spacing } => format!("Dotted{}Px", spacing),
|
||||
LineStyle::Dashed { length } => format!("Dashed{}Px", length),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// A horizontal line in a plot, filling the full width
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct HLine {
|
||||
@@ -41,6 +128,7 @@ pub struct HLine {
|
||||
pub(super) stroke: Stroke,
|
||||
pub(super) name: String,
|
||||
pub(super) highlight: bool,
|
||||
pub(super) style: LineStyle,
|
||||
}
|
||||
|
||||
impl HLine {
|
||||
@@ -50,10 +138,17 @@ impl HLine {
|
||||
stroke: Stroke::new(1.0, Color32::TRANSPARENT),
|
||||
name: String::default(),
|
||||
highlight: false,
|
||||
style: LineStyle::Solid,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the stroke.
|
||||
/// Highlight this line in the plot by scaling up the line.
|
||||
pub fn highlight(mut self) -> Self {
|
||||
self.highlight = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a stroke.
|
||||
pub fn stroke(mut self, stroke: impl Into<Stroke>) -> Self {
|
||||
self.stroke = stroke.into();
|
||||
self
|
||||
@@ -71,6 +166,12 @@ impl HLine {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the line's style. Default is `LineStyle::Solid`.
|
||||
pub fn style(mut self, style: LineStyle) -> Self {
|
||||
self.style = style;
|
||||
self
|
||||
}
|
||||
|
||||
/// Name of this horizontal line.
|
||||
///
|
||||
/// This name will show up in the plot legend, if legends are turned on.
|
||||
@@ -88,18 +189,16 @@ impl PlotItem for HLine {
|
||||
fn get_shapes(&self, _ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec<Shape>) {
|
||||
let HLine {
|
||||
y,
|
||||
mut stroke,
|
||||
stroke,
|
||||
highlight,
|
||||
style,
|
||||
..
|
||||
} = self;
|
||||
if *highlight {
|
||||
stroke.width *= 2.0;
|
||||
}
|
||||
let points = [
|
||||
let points = vec![
|
||||
transform.position_from_value(&Value::new(transform.bounds().min[0], *y)),
|
||||
transform.position_from_value(&Value::new(transform.bounds().max[0], *y)),
|
||||
];
|
||||
shapes.push(Shape::line_segment(points, stroke));
|
||||
style.style_line(points, *stroke, *highlight, shapes);
|
||||
}
|
||||
|
||||
fn initialize(&mut self, _x_range: RangeInclusive<f64>) {}
|
||||
@@ -139,6 +238,7 @@ pub struct VLine {
|
||||
pub(super) stroke: Stroke,
|
||||
pub(super) name: String,
|
||||
pub(super) highlight: bool,
|
||||
pub(super) style: LineStyle,
|
||||
}
|
||||
|
||||
impl VLine {
|
||||
@@ -148,10 +248,17 @@ impl VLine {
|
||||
stroke: Stroke::new(1.0, Color32::TRANSPARENT),
|
||||
name: String::default(),
|
||||
highlight: false,
|
||||
style: LineStyle::Solid,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the stroke.
|
||||
/// Highlight this line in the plot by scaling up the line.
|
||||
pub fn highlight(mut self) -> Self {
|
||||
self.highlight = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a stroke.
|
||||
pub fn stroke(mut self, stroke: impl Into<Stroke>) -> Self {
|
||||
self.stroke = stroke.into();
|
||||
self
|
||||
@@ -169,6 +276,12 @@ impl VLine {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the line's style. Default is `LineStyle::Solid`.
|
||||
pub fn style(mut self, style: LineStyle) -> Self {
|
||||
self.style = style;
|
||||
self
|
||||
}
|
||||
|
||||
/// Name of this vertical line.
|
||||
///
|
||||
/// This name will show up in the plot legend, if legends are turned on.
|
||||
@@ -186,18 +299,16 @@ impl PlotItem for VLine {
|
||||
fn get_shapes(&self, _ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec<Shape>) {
|
||||
let VLine {
|
||||
x,
|
||||
mut stroke,
|
||||
stroke,
|
||||
highlight,
|
||||
style,
|
||||
..
|
||||
} = self;
|
||||
if *highlight {
|
||||
stroke.width *= 2.0;
|
||||
}
|
||||
let points = [
|
||||
let points = vec![
|
||||
transform.position_from_value(&Value::new(*x, transform.bounds().min[1])),
|
||||
transform.position_from_value(&Value::new(*x, transform.bounds().max[1])),
|
||||
];
|
||||
shapes.push(Shape::line_segment(points, stroke));
|
||||
style.style_line(points, *stroke, *highlight, shapes)
|
||||
}
|
||||
|
||||
fn initialize(&mut self, _x_range: RangeInclusive<f64>) {}
|
||||
@@ -409,8 +520,8 @@ pub enum MarkerShape {
|
||||
|
||||
impl MarkerShape {
|
||||
/// Get a vector containing all marker shapes.
|
||||
pub fn all() -> Vec<Self> {
|
||||
vec![
|
||||
pub fn all() -> impl Iterator<Item = MarkerShape> {
|
||||
[
|
||||
Self::Circle,
|
||||
Self::Diamond,
|
||||
Self::Square,
|
||||
@@ -422,6 +533,8 @@ impl MarkerShape {
|
||||
Self::Right,
|
||||
Self::Asterisk,
|
||||
]
|
||||
.iter()
|
||||
.copied()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -432,6 +545,7 @@ pub struct Line {
|
||||
pub(super) name: String,
|
||||
pub(super) highlight: bool,
|
||||
pub(super) fill: Option<f32>,
|
||||
pub(super) style: LineStyle,
|
||||
}
|
||||
|
||||
impl Line {
|
||||
@@ -442,10 +556,11 @@ impl Line {
|
||||
name: Default::default(),
|
||||
highlight: false,
|
||||
fill: None,
|
||||
style: LineStyle::Solid,
|
||||
}
|
||||
}
|
||||
|
||||
/// Highlight this line in the plot by scaling up the line and marker size.
|
||||
/// Highlight this line in the plot by scaling up the line.
|
||||
pub fn highlight(mut self) -> Self {
|
||||
self.highlight = true;
|
||||
self
|
||||
@@ -475,6 +590,12 @@ impl Line {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the line's style. Default is `LineStyle::Solid`.
|
||||
pub fn style(mut self, style: LineStyle) -> Self {
|
||||
self.style = style;
|
||||
self
|
||||
}
|
||||
|
||||
/// Name of this line.
|
||||
///
|
||||
/// This name will show up in the plot legend, if legends are turned on.
|
||||
@@ -499,19 +620,13 @@ impl PlotItem for Line {
|
||||
fn get_shapes(&self, _ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec<Shape>) {
|
||||
let Self {
|
||||
series,
|
||||
mut stroke,
|
||||
stroke,
|
||||
highlight,
|
||||
mut fill,
|
||||
style,
|
||||
..
|
||||
} = self;
|
||||
|
||||
let mut fill_alpha = DEFAULT_FILL_ALPHA;
|
||||
|
||||
if *highlight {
|
||||
stroke.width *= 2.0;
|
||||
fill_alpha = (2.0 * fill_alpha).at_most(1.0);
|
||||
}
|
||||
|
||||
let values_tf: Vec<_> = series
|
||||
.values
|
||||
.iter()
|
||||
@@ -524,6 +639,10 @@ impl PlotItem for Line {
|
||||
fill = None;
|
||||
}
|
||||
if let Some(y_reference) = fill {
|
||||
let mut fill_alpha = DEFAULT_FILL_ALPHA;
|
||||
if *highlight {
|
||||
fill_alpha = (2.0 * fill_alpha).at_most(1.0);
|
||||
}
|
||||
let y = transform
|
||||
.position_from_value(&Value::new(0.0, y_reference))
|
||||
.y;
|
||||
@@ -554,13 +673,7 @@ impl PlotItem for Line {
|
||||
mesh.colored_vertex(pos2(last.x, y), fill_color);
|
||||
shapes.push(Shape::Mesh(mesh));
|
||||
}
|
||||
|
||||
let line_shape = if n_values > 1 {
|
||||
Shape::line(values_tf, stroke)
|
||||
} else {
|
||||
Shape::circle_filled(values_tf[0], stroke.width / 2.0, stroke.color)
|
||||
};
|
||||
shapes.push(line_shape);
|
||||
style.style_line(values_tf, *stroke, *highlight, shapes);
|
||||
}
|
||||
|
||||
fn initialize(&mut self, x_range: RangeInclusive<f64>) {
|
||||
@@ -599,6 +712,7 @@ pub struct Polygon {
|
||||
pub(super) name: String,
|
||||
pub(super) highlight: bool,
|
||||
pub(super) fill_alpha: f32,
|
||||
pub(super) style: LineStyle,
|
||||
}
|
||||
|
||||
impl Polygon {
|
||||
@@ -609,6 +723,7 @@ impl Polygon {
|
||||
name: Default::default(),
|
||||
highlight: false,
|
||||
fill_alpha: DEFAULT_FILL_ALPHA,
|
||||
style: LineStyle::Solid,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -643,6 +758,12 @@ impl Polygon {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the outline's style. Default is `LineStyle::Solid`.
|
||||
pub fn style(mut self, style: LineStyle) -> Self {
|
||||
self.style = style;
|
||||
self
|
||||
}
|
||||
|
||||
/// Name of this polygon.
|
||||
///
|
||||
/// This name will show up in the plot legend, if legends are turned on.
|
||||
@@ -660,18 +781,18 @@ impl PlotItem for Polygon {
|
||||
fn get_shapes(&self, _ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec<Shape>) {
|
||||
let Self {
|
||||
series,
|
||||
mut stroke,
|
||||
stroke,
|
||||
highlight,
|
||||
mut fill_alpha,
|
||||
style,
|
||||
..
|
||||
} = self;
|
||||
|
||||
if *highlight {
|
||||
stroke.width *= 2.0;
|
||||
fill_alpha = (2.0 * fill_alpha).at_most(1.0);
|
||||
}
|
||||
|
||||
let values_tf: Vec<_> = series
|
||||
let mut values_tf: Vec<_> = series
|
||||
.values
|
||||
.iter()
|
||||
.map(|v| transform.position_from_value(v))
|
||||
@@ -679,9 +800,15 @@ impl PlotItem for Polygon {
|
||||
|
||||
let fill = Rgba::from(stroke.color).to_opaque().multiply(fill_alpha);
|
||||
|
||||
let shape = Shape::convex_polygon(values_tf, fill, stroke);
|
||||
|
||||
let shape = Shape::Path {
|
||||
points: values_tf.clone(),
|
||||
closed: true,
|
||||
fill: fill.into(),
|
||||
stroke: Stroke::none(),
|
||||
};
|
||||
shapes.push(shape);
|
||||
values_tf.push(*values_tf.first().unwrap());
|
||||
style.style_line(values_tf, *stroke, *highlight, shapes);
|
||||
}
|
||||
|
||||
fn initialize(&mut self, x_range: RangeInclusive<f64>) {
|
||||
|
||||
@@ -7,8 +7,10 @@ mod transform;
|
||||
use std::collections::HashSet;
|
||||
|
||||
use items::PlotItem;
|
||||
pub use items::{Arrows, Line, MarkerShape, PlotImage, Points, Polygon, Text, Value, Values};
|
||||
pub use items::{HLine, VLine};
|
||||
pub use items::{
|
||||
Arrows, HLine, Line, LineStyle, MarkerShape, PlotImage, Points, Polygon, Text, VLine, Value,
|
||||
Values,
|
||||
};
|
||||
use legend::LegendWidget;
|
||||
pub use legend::{Corner, Legend};
|
||||
use transform::{Bounds, ScreenTransform};
|
||||
|
||||
Reference in New Issue
Block a user