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

Plotting: Add line markers #363

Merged
merged 32 commits into from
May 27, 2021
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
2ced76e
initial work on markers
EmbersArc May 9, 2021
0d09622
clippy fix
EmbersArc May 9, 2021
10ffbfc
simplify marker
EmbersArc May 11, 2021
edd7e17
Merge branch 'master' into plot-markers
EmbersArc May 11, 2021
4ad97f3
use option for color
EmbersArc May 11, 2021
5fb8820
prepare for more demo plots
EmbersArc May 11, 2021
ee5fd49
more improvements for markers
EmbersArc May 11, 2021
99cba8a
some small adjustments
EmbersArc May 11, 2021
10e56e0
better highlighting
EmbersArc May 14, 2021
75c5c1a
don't draw transparent lines
EmbersArc May 14, 2021
90622a5
use transparent color instead of option
EmbersArc May 14, 2021
729ce17
don't brighten curves when highlighting
EmbersArc May 14, 2021
c5945f2
Merge branch 'master' into plot-markers
EmbersArc May 16, 2021
1d7b252
update changelog
EmbersArc May 16, 2021
e62a816
avoid allocations and use line_segment
EmbersArc May 21, 2021
a4a3793
compare against transparent color
EmbersArc May 21, 2021
e4a7e56
Merge remote-tracking branch 'upstream/master' into plot-markers
EmbersArc May 21, 2021
ae00424
create new Points primitive
EmbersArc May 24, 2021
773be05
fix doctest
EmbersArc May 24, 2021
6831302
some cleanup and fix hover
EmbersArc May 24, 2021
4ee57fd
common interface for lines and points
EmbersArc May 24, 2021
4c375f0
clippy fixes
EmbersArc May 24, 2021
95bfbbf
reduce visibilities
EmbersArc May 24, 2021
88fae19
Update egui/src/widgets/plot/mod.rs
EmbersArc May 26, 2021
8bc576b
Update egui/src/widgets/plot/mod.rs
EmbersArc May 26, 2021
c6423be
Update egui_demo_lib/src/apps/demo/plot_demo.rs
EmbersArc May 26, 2021
3aeb354
Update egui_demo_lib/src/apps/demo/plot_demo.rs
EmbersArc May 26, 2021
e76a679
changes based on review
EmbersArc May 26, 2021
8576e2a
Merge remote-tracking branch 'upstream/master' into plot-markers
EmbersArc May 26, 2021
43b4539
fix test
EmbersArc May 26, 2021
c541952
dynamic plot size
EmbersArc May 26, 2021
4197bd1
remove height
EmbersArc May 27, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ NOTE: [`eframe`](eframe/CHANGELOG.md), [`egui_web`](egui_web/CHANGELOG.md) and [


## Unreleased

* [Line markers for plots](https://github.com/emilk/egui/pull/363).

## 0.12.0 - 2021-05-10 - Multitouch, user memory, window pivots, and improved plots

Expand Down
342 changes: 336 additions & 6 deletions egui/src/widgets/plot/items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,317 @@ struct ExplicitGenerator {

// ----------------------------------------------------------------------------

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub(crate) enum MarkerShape {
Circle,
Diamond,
Square,
Cross,
Plus,
Up,
Down,
Left,
Right,
Asterisk,
}

#[derive(Debug, Clone, Copy)]
pub struct Marker {
pub(crate) shape: MarkerShape,
/// Color of the marker. `Color32::TRANSPARENT` means that it will be picked automatically.
pub(crate) color: Color32,
/// Whether to fill the marker. Does not apply to all types.
pub(crate) filled: bool,
/// The maximum extent of the marker from its center.
pub(crate) radius: f32,
}

impl Default for Marker {
fn default() -> Self {
Self {
shape: MarkerShape::Circle,
color: Color32::TRANSPARENT,
filled: true,
radius: 2.0,
}
}
}

impl Marker {
/// Get a vector containing a marker of each shape.
pub fn all() -> Vec<Self> {
vec![
Self::circle(),
Self::diamond(),
Self::square(),
Self::cross(),
Self::plus(),
Self::up(),
Self::down(),
Self::left(),
Self::right(),
Self::asterisk(),
]
}

pub fn circle() -> Self {
Self {
shape: MarkerShape::Circle,
..Default::default()
}
}

pub fn diamond() -> Self {
Self {
shape: MarkerShape::Diamond,
..Default::default()
}
}

pub fn square() -> Self {
Self {
shape: MarkerShape::Square,
..Default::default()
}
}

pub fn cross() -> Self {
Self {
shape: MarkerShape::Cross,
..Default::default()
}
}

pub fn plus() -> Self {
Self {
shape: MarkerShape::Plus,
..Default::default()
}
}

pub fn up() -> Self {
Self {
shape: MarkerShape::Up,
..Default::default()
}
}

pub fn down() -> Self {
Self {
shape: MarkerShape::Down,
..Default::default()
}
}

pub fn left() -> Self {
Self {
shape: MarkerShape::Left,
..Default::default()
}
}

pub fn right() -> Self {
Self {
shape: MarkerShape::Right,
..Default::default()
}
}

pub fn asterisk() -> Self {
Self {
shape: MarkerShape::Asterisk,
..Default::default()
}
}

/// Set the marker's color. Defaults to the curve's color.
pub fn color(mut self, color: Color32) -> Self {
self.color = color;
self
}

/// Whether to fill the marker.
pub fn filled(mut self, filled: bool) -> Self {
self.filled = filled;
self
}

/// Set the maximum extent of the marker around its position.
pub fn radius(mut self, radius: f32) -> Self {
self.radius = radius;
self
}

pub(crate) fn get_shapes(&self, position: &Pos2) -> Vec<Shape> {
EmbersArc marked this conversation as resolved.
Show resolved Hide resolved
let sqrt_3 = 3f32.sqrt();
let frac_sqrt_3_2 = 3f32.sqrt() / 2.0;
let frac_1_sqrt_2 = 1.0 / 2f32.sqrt();

let Self {
color,
filled,
shape,
radius,
} = *self;

if color == Color32::TRANSPARENT {
return Vec::new();
}

let stroke_size = radius / 5.0;

let tf = |offset: Vec<Vec2>| -> Vec<Pos2> {
EmbersArc marked this conversation as resolved.
Show resolved Hide resolved
offset
.into_iter()
.map(|offset| *position + radius * offset)
.collect()
};

let default_stroke = Stroke::new(stroke_size, color);
let stroke = (!filled).then(|| default_stroke).unwrap_or_default();
let fill = filled.then(|| color).unwrap_or_default();

match shape {
MarkerShape::Circle => {
vec![Shape::Circle {
center: *position,
radius,
fill,
stroke,
}]
}
MarkerShape::Diamond => {
let offsets = vec![
vec2(1.0, 0.0),
vec2(0.0, -1.0),
vec2(-1.0, 0.0),
vec2(0.0, 1.0),
];
let points = tf(offsets);
vec![Shape::Path {
points,
closed: true,
fill,
stroke,
}]
}
MarkerShape::Square => {
let offsets = vec![
vec2(frac_1_sqrt_2, frac_1_sqrt_2),
vec2(frac_1_sqrt_2, -frac_1_sqrt_2),
vec2(-frac_1_sqrt_2, -frac_1_sqrt_2),
vec2(-frac_1_sqrt_2, frac_1_sqrt_2),
];
let points = tf(offsets);
vec![Shape::Path {
points,
closed: true,
fill,
stroke,
}]
}
MarkerShape::Cross => {
let diagonal1 = tf(vec![
vec2(-frac_1_sqrt_2, -frac_1_sqrt_2),
vec2(frac_1_sqrt_2, frac_1_sqrt_2),
]);
let diagonal2 = tf(vec![
vec2(frac_1_sqrt_2, -frac_1_sqrt_2),
vec2(-frac_1_sqrt_2, frac_1_sqrt_2),
]);
vec![
Shape::line(diagonal1, default_stroke),
Shape::line(diagonal2, default_stroke),
EmbersArc marked this conversation as resolved.
Show resolved Hide resolved
]
}
MarkerShape::Plus => {
let horizontal = tf(vec![vec2(-1.0, 0.0), vec2(1.0, 0.0)]);
let vertical = tf(vec![vec2(0.0, -1.0), vec2(0.0, 1.0)]);
vec![
Shape::line(horizontal, default_stroke),
Shape::line(vertical, default_stroke),
EmbersArc marked this conversation as resolved.
Show resolved Hide resolved
]
}
MarkerShape::Up => {
let offsets = vec![
vec2(0.0, -1.0),
vec2(-0.5 * sqrt_3, 0.5),
vec2(0.5 * sqrt_3, 0.5),
];
let points = tf(offsets);
vec![Shape::Path {
points,
closed: true,
fill,
stroke,
}]
}
MarkerShape::Down => {
let offsets = vec![
vec2(0.0, 1.0),
vec2(-0.5 * sqrt_3, -0.5),
vec2(0.5 * sqrt_3, -0.5),
];
let points = tf(offsets);
vec![Shape::Path {
points,
closed: true,
fill,
stroke,
}]
}
MarkerShape::Left => {
let offsets = vec![
vec2(-1.0, 0.0),
vec2(0.5, -0.5 * sqrt_3),
vec2(0.5, 0.5 * sqrt_3),
];
let points = tf(offsets);
vec![Shape::Path {
points,
closed: true,
fill,
stroke,
}]
}
MarkerShape::Right => {
let offsets = vec![
vec2(1.0, 0.0),
vec2(-0.5, -0.5 * sqrt_3),
vec2(-0.5, 0.5 * sqrt_3),
];
let points = tf(offsets);
vec![Shape::Path {
points,
closed: true,
fill,
stroke,
}]
}
MarkerShape::Asterisk => {
let vertical = tf(vec![vec2(0.0, -1.0), vec2(0.0, 1.0)]);
let diagonal1 = tf(vec![vec2(-frac_sqrt_3_2, 0.5), vec2(frac_sqrt_3_2, -0.5)]);
let diagonal2 = tf(vec![vec2(-frac_sqrt_3_2, -0.5), vec2(frac_sqrt_3_2, 0.5)]);
vec![
Shape::line(vertical, default_stroke),
Shape::line(diagonal1, default_stroke),
Shape::line(diagonal2, default_stroke),
EmbersArc marked this conversation as resolved.
Show resolved Hide resolved
]
}
}
}
}

/// A series of values forming a path.
pub struct Curve {
pub(crate) values: Vec<Value>,
generator: Option<ExplicitGenerator>,
pub(crate) bounds: Bounds,
pub(crate) stroke: Stroke,
pub(crate) marker: Option<Marker>,
pub(crate) color: Option<Color32>,
pub(crate) width: f32,
emilk marked this conversation as resolved.
Show resolved Hide resolved
emilk marked this conversation as resolved.
Show resolved Hide resolved
pub(crate) name: String,
pub(crate) highlight: bool,
}

impl Curve {
Expand All @@ -88,8 +392,11 @@ impl Curve {
values: Vec::new(),
generator: None,
bounds: Bounds::NOTHING,
stroke: Stroke::new(2.0, Color32::TRANSPARENT),
marker: None,
color: None,
width: 1.0,
name: Default::default(),
highlight: false,
}
}

Expand Down Expand Up @@ -196,21 +503,35 @@ impl Curve {
Self::from_values(values)
}

/// Highlight this curve in the plot by scaling up the line and marker size.
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();
let stroke: Stroke = stroke.into();
self.color = Some(stroke.color);
self.width = stroke.width;
self
}

/// Add a marker for all data points.
pub fn marker(mut self, marker: Marker) -> Self {
self.marker = Some(marker);
self
}

/// Stroke width. A high value means the plot thickens.
pub fn width(mut self, width: f32) -> Self {
self.stroke.width = width;
self.width = width;
self
}

/// Stroke color. Default is `Color32::TRANSPARENT` which means a color will be auto-assigned.
Copy link
Owner

Choose a reason for hiding this comment

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

Is this no longer true?

/// Stroke color.
pub fn color(mut self, color: impl Into<Color32>) -> Self {
self.stroke.color = color.into();
self.color = Some(color.into());
self
}

Expand All @@ -223,4 +544,13 @@ impl Curve {
self.name = name.to_string();
self
}

/// Return the color by which the curve can be identified.
pub(crate) fn get_color(&self) -> Option<Color32> {
self.color.filter(|color| color.a() != 0).or_else(|| {
self.marker
.map(|marker| marker.color)
.filter(|color| *color != Color32::TRANSPARENT)
})
emilk marked this conversation as resolved.
Show resolved Hide resolved
}
}
Loading