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:
parent
d8b2b50780
commit
7c5a2d60c5
5 changed files with 283 additions and 43 deletions
|
@ -8,6 +8,8 @@ NOTE: [`eframe`](eframe/CHANGELOG.md), [`egui_web`](egui_web/CHANGELOG.md) and [
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
### Added ⭐
|
### Added ⭐
|
||||||
|
* Plot:
|
||||||
|
* [Line styles](https://github.com/emilk/egui/pull/482)
|
||||||
* [Progress bar](https://github.com/emilk/egui/pull/519)
|
* [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`.
|
* `Grid::num_columns`: allow the last column to take up the rest of the space of the parent `Ui`.
|
||||||
|
|
||||||
|
|
|
@ -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
|
/// A horizontal line in a plot, filling the full width
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct HLine {
|
pub struct HLine {
|
||||||
|
@ -41,6 +128,7 @@ pub struct HLine {
|
||||||
pub(super) stroke: Stroke,
|
pub(super) stroke: Stroke,
|
||||||
pub(super) name: String,
|
pub(super) name: String,
|
||||||
pub(super) highlight: bool,
|
pub(super) highlight: bool,
|
||||||
|
pub(super) style: LineStyle,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HLine {
|
impl HLine {
|
||||||
|
@ -50,10 +138,17 @@ impl HLine {
|
||||||
stroke: Stroke::new(1.0, Color32::TRANSPARENT),
|
stroke: Stroke::new(1.0, Color32::TRANSPARENT),
|
||||||
name: String::default(),
|
name: String::default(),
|
||||||
highlight: false,
|
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 {
|
pub fn stroke(mut self, stroke: impl Into<Stroke>) -> Self {
|
||||||
self.stroke = stroke.into();
|
self.stroke = stroke.into();
|
||||||
self
|
self
|
||||||
|
@ -71,6 +166,12 @@ impl HLine {
|
||||||
self
|
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.
|
/// Name of this horizontal line.
|
||||||
///
|
///
|
||||||
/// This name will show up in the plot legend, if legends are turned on.
|
/// 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>) {
|
fn get_shapes(&self, _ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec<Shape>) {
|
||||||
let HLine {
|
let HLine {
|
||||||
y,
|
y,
|
||||||
mut stroke,
|
stroke,
|
||||||
highlight,
|
highlight,
|
||||||
|
style,
|
||||||
..
|
..
|
||||||
} = self;
|
} = self;
|
||||||
if *highlight {
|
let points = vec![
|
||||||
stroke.width *= 2.0;
|
|
||||||
}
|
|
||||||
let points = [
|
|
||||||
transform.position_from_value(&Value::new(transform.bounds().min[0], *y)),
|
transform.position_from_value(&Value::new(transform.bounds().min[0], *y)),
|
||||||
transform.position_from_value(&Value::new(transform.bounds().max[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>) {}
|
fn initialize(&mut self, _x_range: RangeInclusive<f64>) {}
|
||||||
|
@ -139,6 +238,7 @@ pub struct VLine {
|
||||||
pub(super) stroke: Stroke,
|
pub(super) stroke: Stroke,
|
||||||
pub(super) name: String,
|
pub(super) name: String,
|
||||||
pub(super) highlight: bool,
|
pub(super) highlight: bool,
|
||||||
|
pub(super) style: LineStyle,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VLine {
|
impl VLine {
|
||||||
|
@ -148,10 +248,17 @@ impl VLine {
|
||||||
stroke: Stroke::new(1.0, Color32::TRANSPARENT),
|
stroke: Stroke::new(1.0, Color32::TRANSPARENT),
|
||||||
name: String::default(),
|
name: String::default(),
|
||||||
highlight: false,
|
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 {
|
pub fn stroke(mut self, stroke: impl Into<Stroke>) -> Self {
|
||||||
self.stroke = stroke.into();
|
self.stroke = stroke.into();
|
||||||
self
|
self
|
||||||
|
@ -169,6 +276,12 @@ impl VLine {
|
||||||
self
|
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.
|
/// Name of this vertical line.
|
||||||
///
|
///
|
||||||
/// This name will show up in the plot legend, if legends are turned on.
|
/// 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>) {
|
fn get_shapes(&self, _ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec<Shape>) {
|
||||||
let VLine {
|
let VLine {
|
||||||
x,
|
x,
|
||||||
mut stroke,
|
stroke,
|
||||||
highlight,
|
highlight,
|
||||||
|
style,
|
||||||
..
|
..
|
||||||
} = self;
|
} = self;
|
||||||
if *highlight {
|
let points = vec![
|
||||||
stroke.width *= 2.0;
|
|
||||||
}
|
|
||||||
let points = [
|
|
||||||
transform.position_from_value(&Value::new(*x, transform.bounds().min[1])),
|
transform.position_from_value(&Value::new(*x, transform.bounds().min[1])),
|
||||||
transform.position_from_value(&Value::new(*x, transform.bounds().max[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>) {}
|
fn initialize(&mut self, _x_range: RangeInclusive<f64>) {}
|
||||||
|
@ -409,8 +520,8 @@ pub enum MarkerShape {
|
||||||
|
|
||||||
impl MarkerShape {
|
impl MarkerShape {
|
||||||
/// Get a vector containing all marker shapes.
|
/// Get a vector containing all marker shapes.
|
||||||
pub fn all() -> Vec<Self> {
|
pub fn all() -> impl Iterator<Item = MarkerShape> {
|
||||||
vec![
|
[
|
||||||
Self::Circle,
|
Self::Circle,
|
||||||
Self::Diamond,
|
Self::Diamond,
|
||||||
Self::Square,
|
Self::Square,
|
||||||
|
@ -422,6 +533,8 @@ impl MarkerShape {
|
||||||
Self::Right,
|
Self::Right,
|
||||||
Self::Asterisk,
|
Self::Asterisk,
|
||||||
]
|
]
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -432,6 +545,7 @@ pub struct Line {
|
||||||
pub(super) name: String,
|
pub(super) name: String,
|
||||||
pub(super) highlight: bool,
|
pub(super) highlight: bool,
|
||||||
pub(super) fill: Option<f32>,
|
pub(super) fill: Option<f32>,
|
||||||
|
pub(super) style: LineStyle,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Line {
|
impl Line {
|
||||||
|
@ -442,10 +556,11 @@ impl Line {
|
||||||
name: Default::default(),
|
name: Default::default(),
|
||||||
highlight: false,
|
highlight: false,
|
||||||
fill: None,
|
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 {
|
pub fn highlight(mut self) -> Self {
|
||||||
self.highlight = true;
|
self.highlight = true;
|
||||||
self
|
self
|
||||||
|
@ -475,6 +590,12 @@ impl Line {
|
||||||
self
|
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.
|
/// Name of this line.
|
||||||
///
|
///
|
||||||
/// This name will show up in the plot legend, if legends are turned on.
|
/// 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>) {
|
fn get_shapes(&self, _ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec<Shape>) {
|
||||||
let Self {
|
let Self {
|
||||||
series,
|
series,
|
||||||
mut stroke,
|
stroke,
|
||||||
highlight,
|
highlight,
|
||||||
mut fill,
|
mut fill,
|
||||||
|
style,
|
||||||
..
|
..
|
||||||
} = self;
|
} = 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
|
let values_tf: Vec<_> = series
|
||||||
.values
|
.values
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -524,6 +639,10 @@ impl PlotItem for Line {
|
||||||
fill = None;
|
fill = None;
|
||||||
}
|
}
|
||||||
if let Some(y_reference) = fill {
|
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
|
let y = transform
|
||||||
.position_from_value(&Value::new(0.0, y_reference))
|
.position_from_value(&Value::new(0.0, y_reference))
|
||||||
.y;
|
.y;
|
||||||
|
@ -554,13 +673,7 @@ impl PlotItem for Line {
|
||||||
mesh.colored_vertex(pos2(last.x, y), fill_color);
|
mesh.colored_vertex(pos2(last.x, y), fill_color);
|
||||||
shapes.push(Shape::Mesh(mesh));
|
shapes.push(Shape::Mesh(mesh));
|
||||||
}
|
}
|
||||||
|
style.style_line(values_tf, *stroke, *highlight, shapes);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn initialize(&mut self, x_range: RangeInclusive<f64>) {
|
fn initialize(&mut self, x_range: RangeInclusive<f64>) {
|
||||||
|
@ -599,6 +712,7 @@ pub struct Polygon {
|
||||||
pub(super) name: String,
|
pub(super) name: String,
|
||||||
pub(super) highlight: bool,
|
pub(super) highlight: bool,
|
||||||
pub(super) fill_alpha: f32,
|
pub(super) fill_alpha: f32,
|
||||||
|
pub(super) style: LineStyle,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Polygon {
|
impl Polygon {
|
||||||
|
@ -609,6 +723,7 @@ impl Polygon {
|
||||||
name: Default::default(),
|
name: Default::default(),
|
||||||
highlight: false,
|
highlight: false,
|
||||||
fill_alpha: DEFAULT_FILL_ALPHA,
|
fill_alpha: DEFAULT_FILL_ALPHA,
|
||||||
|
style: LineStyle::Solid,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -643,6 +758,12 @@ impl Polygon {
|
||||||
self
|
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.
|
/// Name of this polygon.
|
||||||
///
|
///
|
||||||
/// This name will show up in the plot legend, if legends are turned on.
|
/// 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>) {
|
fn get_shapes(&self, _ui: &mut Ui, transform: &ScreenTransform, shapes: &mut Vec<Shape>) {
|
||||||
let Self {
|
let Self {
|
||||||
series,
|
series,
|
||||||
mut stroke,
|
stroke,
|
||||||
highlight,
|
highlight,
|
||||||
mut fill_alpha,
|
mut fill_alpha,
|
||||||
|
style,
|
||||||
..
|
..
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
if *highlight {
|
if *highlight {
|
||||||
stroke.width *= 2.0;
|
|
||||||
fill_alpha = (2.0 * fill_alpha).at_most(1.0);
|
fill_alpha = (2.0 * fill_alpha).at_most(1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
let values_tf: Vec<_> = series
|
let mut values_tf: Vec<_> = series
|
||||||
.values
|
.values
|
||||||
.iter()
|
.iter()
|
||||||
.map(|v| transform.position_from_value(v))
|
.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 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);
|
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>) {
|
fn initialize(&mut self, x_range: RangeInclusive<f64>) {
|
||||||
|
|
|
@ -7,8 +7,10 @@ mod transform;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use items::PlotItem;
|
use items::PlotItem;
|
||||||
pub use items::{Arrows, Line, MarkerShape, PlotImage, Points, Polygon, Text, Value, Values};
|
pub use items::{
|
||||||
pub use items::{HLine, VLine};
|
Arrows, HLine, Line, LineStyle, MarkerShape, PlotImage, Points, Polygon, Text, VLine, Value,
|
||||||
|
Values,
|
||||||
|
};
|
||||||
use legend::LegendWidget;
|
use legend::LegendWidget;
|
||||||
pub use legend::{Corner, Legend};
|
pub use legend::{Corner, Legend};
|
||||||
use transform::{Bounds, ScreenTransform};
|
use transform::{Bounds, ScreenTransform};
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use egui::*;
|
use egui::*;
|
||||||
use plot::{
|
use plot::{
|
||||||
Arrows, Corner, HLine, Legend, Line, MarkerShape, Plot, PlotImage, Points, Polygon, Text,
|
Arrows, Corner, HLine, Legend, Line, LineStyle, MarkerShape, Plot, PlotImage, Points, Polygon,
|
||||||
VLine, Value, Values,
|
Text, VLine, Value, Values,
|
||||||
};
|
};
|
||||||
use std::f64::consts::TAU;
|
use std::f64::consts::TAU;
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ struct LineDemo {
|
||||||
circle_center: Pos2,
|
circle_center: Pos2,
|
||||||
square: bool,
|
square: bool,
|
||||||
proportional: bool,
|
proportional: bool,
|
||||||
|
line_style: LineStyle,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for LineDemo {
|
impl Default for LineDemo {
|
||||||
|
@ -24,6 +25,7 @@ impl Default for LineDemo {
|
||||||
circle_center: Pos2::new(0.0, 0.0),
|
circle_center: Pos2::new(0.0, 0.0),
|
||||||
square: false,
|
square: false,
|
||||||
proportional: true,
|
proportional: true,
|
||||||
|
line_style: LineStyle::Solid,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,6 +39,7 @@ impl LineDemo {
|
||||||
circle_center,
|
circle_center,
|
||||||
square,
|
square,
|
||||||
proportional,
|
proportional,
|
||||||
|
line_style,
|
||||||
..
|
..
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
|
@ -73,6 +76,23 @@ impl LineDemo {
|
||||||
ui.checkbox(proportional, "Proportional data axes")
|
ui.checkbox(proportional, "Proportional data axes")
|
||||||
.on_hover_text("Tick are the same size on both axes.");
|
.on_hover_text("Tick are the same size on both axes.");
|
||||||
});
|
});
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
ComboBox::from_label("Line style")
|
||||||
|
.selected_text(line_style.to_string())
|
||||||
|
.show_ui(ui, |ui| {
|
||||||
|
[
|
||||||
|
LineStyle::Solid,
|
||||||
|
LineStyle::dashed_dense(),
|
||||||
|
LineStyle::dashed_loose(),
|
||||||
|
LineStyle::dotted_dense(),
|
||||||
|
LineStyle::dotted_loose(),
|
||||||
|
]
|
||||||
|
.iter()
|
||||||
|
.for_each(|style| {
|
||||||
|
ui.selectable_value(line_style, *style, style.to_string());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,6 +108,7 @@ impl LineDemo {
|
||||||
});
|
});
|
||||||
Line::new(Values::from_values_iter(circle))
|
Line::new(Values::from_values_iter(circle))
|
||||||
.color(Color32::from_rgb(100, 200, 100))
|
.color(Color32::from_rgb(100, 200, 100))
|
||||||
|
.style(self.line_style)
|
||||||
.name("circle")
|
.name("circle")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,6 +120,7 @@ impl LineDemo {
|
||||||
512,
|
512,
|
||||||
))
|
))
|
||||||
.color(Color32::from_rgb(200, 100, 100))
|
.color(Color32::from_rgb(200, 100, 100))
|
||||||
|
.style(self.line_style)
|
||||||
.name("wave")
|
.name("wave")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,6 +132,7 @@ impl LineDemo {
|
||||||
256,
|
256,
|
||||||
))
|
))
|
||||||
.color(Color32::from_rgb(100, 150, 250))
|
.color(Color32::from_rgb(100, 150, 250))
|
||||||
|
.style(self.line_style)
|
||||||
.name("x = sin(2t), y = sin(3t)")
|
.name("x = sin(2t), y = sin(3t)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -158,7 +181,6 @@ impl Default for MarkerDemo {
|
||||||
impl MarkerDemo {
|
impl MarkerDemo {
|
||||||
fn markers(&self) -> Vec<Points> {
|
fn markers(&self) -> Vec<Points> {
|
||||||
MarkerShape::all()
|
MarkerShape::all()
|
||||||
.into_iter()
|
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, marker)| {
|
.map(|(i, marker)| {
|
||||||
let y_offset = i as f32 * 0.5 + 1.0;
|
let y_offset = i as f32 * 0.5 + 1.0;
|
||||||
|
|
|
@ -66,7 +66,7 @@ impl Shape {
|
||||||
|
|
||||||
/// A line through many points.
|
/// A line through many points.
|
||||||
///
|
///
|
||||||
/// Use [`Self::line_segment`] instead if your line only connect two points.
|
/// Use [`Self::line_segment`] instead if your line only connects two points.
|
||||||
pub fn line(points: Vec<Pos2>, stroke: impl Into<Stroke>) -> Self {
|
pub fn line(points: Vec<Pos2>, stroke: impl Into<Stroke>) -> Self {
|
||||||
Self::Path {
|
Self::Path {
|
||||||
points,
|
points,
|
||||||
|
@ -86,6 +86,30 @@ impl Shape {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Turn a line into equally spaced dots.
|
||||||
|
pub fn dotted_line(
|
||||||
|
points: &[Pos2],
|
||||||
|
color: impl Into<Color32>,
|
||||||
|
spacing: f32,
|
||||||
|
radius: f32,
|
||||||
|
) -> Vec<Self> {
|
||||||
|
let mut shapes = Vec::new();
|
||||||
|
points_from_line(points, spacing, radius, color.into(), &mut shapes);
|
||||||
|
shapes
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Turn a line into dashes.
|
||||||
|
pub fn dashed_line(
|
||||||
|
points: &[Pos2],
|
||||||
|
stroke: impl Into<Stroke>,
|
||||||
|
dash_length: f32,
|
||||||
|
gap_length: f32,
|
||||||
|
) -> Vec<Self> {
|
||||||
|
let mut shapes = Vec::new();
|
||||||
|
dashes_from_line(points, stroke.into(), dash_length, gap_length, &mut shapes);
|
||||||
|
shapes
|
||||||
|
}
|
||||||
|
|
||||||
/// A convex polygon with a fill and optional stroke.
|
/// A convex polygon with a fill and optional stroke.
|
||||||
pub fn convex_polygon(
|
pub fn convex_polygon(
|
||||||
points: Vec<Pos2>,
|
points: Vec<Pos2>,
|
||||||
|
@ -161,6 +185,69 @@ impl Shape {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates equally spaced filled circles from a line.
|
||||||
|
fn points_from_line(
|
||||||
|
line: &[Pos2],
|
||||||
|
spacing: f32,
|
||||||
|
radius: f32,
|
||||||
|
color: Color32,
|
||||||
|
shapes: &mut Vec<Shape>,
|
||||||
|
) {
|
||||||
|
let mut position_on_segment = 0.0;
|
||||||
|
line.windows(2).for_each(|window| {
|
||||||
|
let start = window[0];
|
||||||
|
let end = window[1];
|
||||||
|
let vector = end - start;
|
||||||
|
let segment_length = vector.length();
|
||||||
|
while position_on_segment < segment_length {
|
||||||
|
let new_point = start + vector * (position_on_segment / segment_length);
|
||||||
|
shapes.push(Shape::circle_filled(new_point, radius, color));
|
||||||
|
position_on_segment += spacing;
|
||||||
|
}
|
||||||
|
position_on_segment -= segment_length;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates dashes from a line.
|
||||||
|
fn dashes_from_line(
|
||||||
|
line: &[Pos2],
|
||||||
|
stroke: Stroke,
|
||||||
|
dash_length: f32,
|
||||||
|
gap_length: f32,
|
||||||
|
shapes: &mut Vec<Shape>,
|
||||||
|
) {
|
||||||
|
let mut position_on_segment = 0.0;
|
||||||
|
let mut drawing_dash = false;
|
||||||
|
line.windows(2).for_each(|window| {
|
||||||
|
let start = window[0];
|
||||||
|
let end = window[1];
|
||||||
|
let vector = end - start;
|
||||||
|
let segment_length = vector.length();
|
||||||
|
while position_on_segment < segment_length {
|
||||||
|
let new_point = start + vector * (position_on_segment / segment_length);
|
||||||
|
if drawing_dash {
|
||||||
|
// This is the end point.
|
||||||
|
if let Shape::Path { points, .. } = shapes.last_mut().unwrap() {
|
||||||
|
points.push(new_point);
|
||||||
|
}
|
||||||
|
position_on_segment += gap_length;
|
||||||
|
} else {
|
||||||
|
// Start a new dash.
|
||||||
|
shapes.push(Shape::line(vec![new_point], stroke));
|
||||||
|
position_on_segment += dash_length;
|
||||||
|
}
|
||||||
|
drawing_dash = !drawing_dash;
|
||||||
|
}
|
||||||
|
// If the segment ends and the dash is not finished, add the segment's end point.
|
||||||
|
if drawing_dash {
|
||||||
|
if let Shape::Path { points, .. } = shapes.last_mut().unwrap() {
|
||||||
|
points.push(end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
position_on_segment -= segment_length;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// ## Operations
|
/// ## Operations
|
||||||
impl Shape {
|
impl Shape {
|
||||||
pub fn mesh(mesh: Mesh) -> Self {
|
pub fn mesh(mesh: Mesh) -> Self {
|
||||||
|
|
Loading…
Reference in a new issue