Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Plot: Line styles #482

Merged
merged 10 commits into from
Jul 6, 2021
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ NOTE: [`eframe`](eframe/CHANGELOG.md), [`egui_web`](egui_web/CHANGELOG.md) and [
## Unreleased

### Added ⭐
* Plot:
* [Line styles](https://github.com/emilk/egui/pull/482)
* [Progress bar](https://github.com/emilk/egui/pull/519)
* `Grid::num_columns`: allow the last column to take up the rest of the space of the parent `Ui`.

Expand Down
201 changes: 164 additions & 37 deletions egui/src/widgets/plot/items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,101 @@ 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
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hint: let golden_ratio = (5.0_f32.sqrt() + 1.0) / 2.0; // 1.61803398875 to avoid the division later :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, but made redundant by the other change :)

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 {
pub(super) y: f64,
pub(super) stroke: Stroke,
pub(super) name: String,
pub(super) highlight: bool,
pub(super) style: LineStyle,
}

impl HLine {
Expand All @@ -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
Expand All @@ -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.
Expand All @@ -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>) {}
Expand Down Expand Up @@ -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 {
Expand All @@ -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
Expand All @@ -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.
Expand All @@ -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>) {}
Expand Down Expand Up @@ -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,
Expand All @@ -422,6 +533,8 @@ impl MarkerShape {
Self::Right,
Self::Asterisk,
]
.iter()
.copied()
}
}

Expand All @@ -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 {
Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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()
Expand All @@ -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;
Expand Down Expand Up @@ -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>) {
Expand Down Expand Up @@ -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 {
Expand All @@ -609,6 +723,7 @@ impl Polygon {
name: Default::default(),
highlight: false,
fill_alpha: DEFAULT_FILL_ALPHA,
style: LineStyle::Solid,
}
}

Expand Down Expand Up @@ -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.
Expand All @@ -660,28 +781,34 @@ 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))
.collect();

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>) {
Expand Down
Loading