Customize Plot label and cursor texts (#1235)
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
This commit is contained in:
parent
cfad28936d
commit
8f8eb5d4a9
5 changed files with 124 additions and 34 deletions
|
@ -24,6 +24,7 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w
|
||||||
* Added linked axis support for plots via `plot::LinkedAxisGroup` ([#1184](https://github.com/emilk/egui/pull/1184)).
|
* Added linked axis support for plots via `plot::LinkedAxisGroup` ([#1184](https://github.com/emilk/egui/pull/1184)).
|
||||||
* Added `Response::on_hover_text_at_pointer` as a convenience akin to `Response::on_hover_text` ([1179](https://github.com/emilk/egui/pull/1179)).
|
* Added `Response::on_hover_text_at_pointer` as a convenience akin to `Response::on_hover_text` ([1179](https://github.com/emilk/egui/pull/1179)).
|
||||||
* Added `ui.weak(text)`.
|
* Added `ui.weak(text)`.
|
||||||
|
* Added plot pointer coordinates with `Plot::coordinates_formatter`. ([#1235](https://github.com/emilk/egui/pull/1235)).
|
||||||
* Added `Slider::step_by` ([1255](https://github.com/emilk/egui/pull/1225)).
|
* Added `Slider::step_by` ([1255](https://github.com/emilk/egui/pull/1225)).
|
||||||
* Added ability to scroll an UI into view without specifying an alignment ([1247](https://github.com/emilk/egui/pull/1247))
|
* Added ability to scroll an UI into view without specifying an alignment ([1247](https://github.com/emilk/egui/pull/1247))
|
||||||
|
|
||||||
|
@ -47,6 +48,7 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w
|
||||||
* Replaced Frame's `margin: Vec2` with `margin: Margin`, allowing for different margins on opposing sides ([#1219](https://github.com/emilk/egui/pull/1219)).
|
* Replaced Frame's `margin: Vec2` with `margin: Margin`, allowing for different margins on opposing sides ([#1219](https://github.com/emilk/egui/pull/1219)).
|
||||||
* `Plot::highlight` now takes a `bool` argument ([#1159](https://github.com/emilk/egui/pull/1159)).
|
* `Plot::highlight` now takes a `bool` argument ([#1159](https://github.com/emilk/egui/pull/1159)).
|
||||||
* `ScrollArea::show` now returns a `ScrollAreaOutput`, so you might need to add `.inner` after the call to it ([#1166](https://github.com/emilk/egui/pull/1166)).
|
* `ScrollArea::show` now returns a `ScrollAreaOutput`, so you might need to add `.inner` after the call to it ([#1166](https://github.com/emilk/egui/pull/1166)).
|
||||||
|
* Renamed `Plot::custom_label_func` to `Plot::label_formatter` ([#1235](https://github.com/emilk/egui/pull/1235)).
|
||||||
* Tooltips that don't fit the window don't flicker anymore ([#1240](https://github.com/emilk/egui/pull/1240)).
|
* Tooltips that don't fit the window don't flicker anymore ([#1240](https://github.com/emilk/egui/pull/1240)).
|
||||||
* `Areas::layer_id_at` ignores non interatable layers (i.e. Tooltips) ([#1240](https://github.com/emilk/egui/pull/1240)).
|
* `Areas::layer_id_at` ignores non interatable layers (i.e. Tooltips) ([#1240](https://github.com/emilk/egui/pull/1240)).
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ use epaint::Mesh;
|
||||||
|
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
use super::{CustomLabelFuncRef, PlotBounds, ScreenTransform};
|
use super::{LabelFormatter, PlotBounds, ScreenTransform};
|
||||||
use rect_elem::*;
|
use rect_elem::*;
|
||||||
use values::{ClosestElem, PlotGeometry};
|
use values::{ClosestElem, PlotGeometry};
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@ pub(super) trait PlotItem {
|
||||||
elem: ClosestElem,
|
elem: ClosestElem,
|
||||||
shapes: &mut Vec<Shape>,
|
shapes: &mut Vec<Shape>,
|
||||||
plot: &PlotConfig<'_>,
|
plot: &PlotConfig<'_>,
|
||||||
custom_label_func: &CustomLabelFuncRef,
|
label_formatter: &LabelFormatter,
|
||||||
) {
|
) {
|
||||||
let points = match self.geometry() {
|
let points = match self.geometry() {
|
||||||
PlotGeometry::Points(points) => points,
|
PlotGeometry::Points(points) => points,
|
||||||
|
@ -89,7 +89,7 @@ pub(super) trait PlotItem {
|
||||||
let pointer = plot.transform.position_from_value(&value);
|
let pointer = plot.transform.position_from_value(&value);
|
||||||
shapes.push(Shape::circle_filled(pointer, 3.0, line_color));
|
shapes.push(Shape::circle_filled(pointer, 3.0, line_color));
|
||||||
|
|
||||||
rulers_at_value(pointer, value, self.name(), plot, shapes, custom_label_func);
|
rulers_at_value(pointer, value, self.name(), plot, shapes, label_formatter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1380,7 +1380,7 @@ impl PlotItem for BarChart {
|
||||||
elem: ClosestElem,
|
elem: ClosestElem,
|
||||||
shapes: &mut Vec<Shape>,
|
shapes: &mut Vec<Shape>,
|
||||||
plot: &PlotConfig<'_>,
|
plot: &PlotConfig<'_>,
|
||||||
_: &CustomLabelFuncRef,
|
_: &LabelFormatter,
|
||||||
) {
|
) {
|
||||||
let bar = &self.bars[elem.index];
|
let bar = &self.bars[elem.index];
|
||||||
|
|
||||||
|
@ -1522,7 +1522,7 @@ impl PlotItem for BoxPlot {
|
||||||
elem: ClosestElem,
|
elem: ClosestElem,
|
||||||
shapes: &mut Vec<Shape>,
|
shapes: &mut Vec<Shape>,
|
||||||
plot: &PlotConfig<'_>,
|
plot: &PlotConfig<'_>,
|
||||||
_: &CustomLabelFuncRef,
|
_: &LabelFormatter,
|
||||||
) {
|
) {
|
||||||
let box_plot = &self.boxes[elem.index];
|
let box_plot = &self.boxes[elem.index];
|
||||||
|
|
||||||
|
@ -1643,7 +1643,7 @@ pub(super) fn rulers_at_value(
|
||||||
name: &str,
|
name: &str,
|
||||||
plot: &PlotConfig<'_>,
|
plot: &PlotConfig<'_>,
|
||||||
shapes: &mut Vec<Shape>,
|
shapes: &mut Vec<Shape>,
|
||||||
custom_label_func: &CustomLabelFuncRef,
|
label_formatter: &LabelFormatter,
|
||||||
) {
|
) {
|
||||||
let line_color = rulers_color(plot.ui);
|
let line_color = rulers_color(plot.ui);
|
||||||
if plot.show_x {
|
if plot.show_x {
|
||||||
|
@ -1663,7 +1663,7 @@ pub(super) fn rulers_at_value(
|
||||||
let scale = plot.transform.dvalue_dpos();
|
let scale = plot.transform.dvalue_dpos();
|
||||||
let x_decimals = ((-scale[0].abs().log10()).ceil().at_least(0.0) as usize).at_most(6);
|
let x_decimals = ((-scale[0].abs().log10()).ceil().at_least(0.0) as usize).at_most(6);
|
||||||
let y_decimals = ((-scale[1].abs().log10()).ceil().at_least(0.0) as usize).at_most(6);
|
let y_decimals = ((-scale[1].abs().log10()).ceil().at_least(0.0) as usize).at_most(6);
|
||||||
if let Some(custom_label) = custom_label_func {
|
if let Some(custom_label) = label_formatter {
|
||||||
custom_label(name, &value)
|
custom_label(name, &value)
|
||||||
} else if plot.show_x && plot.show_y {
|
} else if plot.show_x && plot.show_y {
|
||||||
format!(
|
format!(
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
//! Simple plotting library.
|
//! Simple plotting library.
|
||||||
|
|
||||||
use std::{cell::RefCell, rc::Rc};
|
use std::{cell::RefCell, ops::RangeInclusive, rc::Rc};
|
||||||
|
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use epaint::ahash::AHashSet;
|
use epaint::ahash::AHashSet;
|
||||||
|
@ -20,12 +20,44 @@ mod items;
|
||||||
mod legend;
|
mod legend;
|
||||||
mod transform;
|
mod transform;
|
||||||
|
|
||||||
type CustomLabelFunc = dyn Fn(&str, &Value) -> String;
|
type LabelFormatterFn = dyn Fn(&str, &Value) -> String;
|
||||||
type CustomLabelFuncRef = Option<Box<CustomLabelFunc>>;
|
type LabelFormatter = Option<Box<LabelFormatterFn>>;
|
||||||
|
type AxisFormatterFn = dyn Fn(f64, &RangeInclusive<f64>) -> String;
|
||||||
type AxisFormatterFn = dyn Fn(f64) -> String;
|
|
||||||
type AxisFormatter = Option<Box<AxisFormatterFn>>;
|
type AxisFormatter = Option<Box<AxisFormatterFn>>;
|
||||||
|
|
||||||
|
/// Specifies the coordinates formatting when passed to [`Plot::coordinates_formatter`].
|
||||||
|
pub struct CoordinatesFormatter {
|
||||||
|
function: Box<dyn Fn(&Value, &PlotBounds) -> String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CoordinatesFormatter {
|
||||||
|
/// Create a new formatter based on the pointer coordinate and the plot bounds.
|
||||||
|
pub fn new(function: impl Fn(&Value, &PlotBounds) -> String + 'static) -> Self {
|
||||||
|
Self {
|
||||||
|
function: Box::new(function),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Show a fixed number of decimal places.
|
||||||
|
pub fn with_decimals(num_decimals: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
function: Box::new(move |value, _| {
|
||||||
|
format!("x: {:.d$}\ny: {:.d$}", value.x, value.y, d = num_decimals)
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format(&self, value: &Value, bounds: &PlotBounds) -> String {
|
||||||
|
(self.function)(value, bounds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for CoordinatesFormatter {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::with_decimals(3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
/// Information about the plot that has to persist between frames.
|
/// Information about the plot that has to persist between frames.
|
||||||
|
@ -146,7 +178,8 @@ pub struct Plot {
|
||||||
|
|
||||||
show_x: bool,
|
show_x: bool,
|
||||||
show_y: bool,
|
show_y: bool,
|
||||||
custom_label_func: CustomLabelFuncRef,
|
label_formatter: LabelFormatter,
|
||||||
|
coordinates_formatter: Option<(Corner, CoordinatesFormatter)>,
|
||||||
axis_formatters: [AxisFormatter; 2],
|
axis_formatters: [AxisFormatter; 2],
|
||||||
legend_config: Option<Legend>,
|
legend_config: Option<Legend>,
|
||||||
show_background: bool,
|
show_background: bool,
|
||||||
|
@ -177,7 +210,8 @@ impl Plot {
|
||||||
|
|
||||||
show_x: true,
|
show_x: true,
|
||||||
show_y: true,
|
show_y: true,
|
||||||
custom_label_func: None,
|
label_formatter: None,
|
||||||
|
coordinates_formatter: None,
|
||||||
axis_formatters: [None, None], // [None; 2] requires Copy
|
axis_formatters: [None, None], // [None; 2] requires Copy
|
||||||
legend_config: None,
|
legend_config: None,
|
||||||
show_background: true,
|
show_background: true,
|
||||||
|
@ -284,7 +318,7 @@ impl Plot {
|
||||||
/// });
|
/// });
|
||||||
/// let line = Line::new(Values::from_values_iter(sin));
|
/// let line = Line::new(Values::from_values_iter(sin));
|
||||||
/// Plot::new("my_plot").view_aspect(2.0)
|
/// Plot::new("my_plot").view_aspect(2.0)
|
||||||
/// .custom_label_func(|name, value| {
|
/// .label_formatter(|name, value| {
|
||||||
/// if !name.is_empty() {
|
/// if !name.is_empty() {
|
||||||
/// format!("{}: {:.*}%", name, 1, value.y).to_string()
|
/// format!("{}: {:.*}%", name, 1, value.y).to_string()
|
||||||
/// } else {
|
/// } else {
|
||||||
|
@ -294,34 +328,50 @@ impl Plot {
|
||||||
/// .show(ui, |plot_ui| plot_ui.line(line));
|
/// .show(ui, |plot_ui| plot_ui.line(line));
|
||||||
/// # });
|
/// # });
|
||||||
/// ```
|
/// ```
|
||||||
pub fn custom_label_func(
|
pub fn label_formatter(
|
||||||
mut self,
|
mut self,
|
||||||
custom_label_func: impl Fn(&str, &Value) -> String + 'static,
|
label_formatter: impl Fn(&str, &Value) -> String + 'static,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.custom_label_func = Some(Box::new(custom_label_func));
|
self.label_formatter = Some(Box::new(label_formatter));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Provide a function to customize the labels for the X axis.
|
/// Show the pointer coordinates in the plot.
|
||||||
|
pub fn coordinates_formatter(
|
||||||
|
mut self,
|
||||||
|
position: Corner,
|
||||||
|
formatter: CoordinatesFormatter,
|
||||||
|
) -> Self {
|
||||||
|
self.coordinates_formatter = Some((position, formatter));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Provide a function to customize the labels for the X axis based on the current visible value range.
|
||||||
///
|
///
|
||||||
/// This is useful for custom input domains, e.g. date/time.
|
/// This is useful for custom input domains, e.g. date/time.
|
||||||
///
|
///
|
||||||
/// If axis labels should not appear for certain values or beyond a certain zoom/resolution,
|
/// If axis labels should not appear for certain values or beyond a certain zoom/resolution,
|
||||||
/// the formatter function can return empty strings. This is also useful if your domain is
|
/// the formatter function can return empty strings. This is also useful if your domain is
|
||||||
/// discrete (e.g. only full days in a calendar).
|
/// discrete (e.g. only full days in a calendar).
|
||||||
pub fn x_axis_formatter(mut self, func: impl Fn(f64) -> String + 'static) -> Self {
|
pub fn x_axis_formatter(
|
||||||
|
mut self,
|
||||||
|
func: impl Fn(f64, &RangeInclusive<f64>) -> String + 'static,
|
||||||
|
) -> Self {
|
||||||
self.axis_formatters[0] = Some(Box::new(func));
|
self.axis_formatters[0] = Some(Box::new(func));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Provide a function to customize the labels for the Y axis.
|
/// Provide a function to customize the labels for the Y axis based on the current value range.
|
||||||
///
|
///
|
||||||
/// This is useful for custom value representation, e.g. percentage or units.
|
/// This is useful for custom value representation, e.g. percentage or units.
|
||||||
///
|
///
|
||||||
/// If axis labels should not appear for certain values or beyond a certain zoom/resolution,
|
/// If axis labels should not appear for certain values or beyond a certain zoom/resolution,
|
||||||
/// the formatter function can return empty strings. This is also useful if your Y values are
|
/// the formatter function can return empty strings. This is also useful if your Y values are
|
||||||
/// discrete (e.g. only integers).
|
/// discrete (e.g. only integers).
|
||||||
pub fn y_axis_formatter(mut self, func: impl Fn(f64) -> String + 'static) -> Self {
|
pub fn y_axis_formatter(
|
||||||
|
mut self,
|
||||||
|
func: impl Fn(f64, &RangeInclusive<f64>) -> String + 'static,
|
||||||
|
) -> Self {
|
||||||
self.axis_formatters[1] = Some(Box::new(func));
|
self.axis_formatters[1] = Some(Box::new(func));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -388,7 +438,8 @@ impl Plot {
|
||||||
view_aspect,
|
view_aspect,
|
||||||
mut show_x,
|
mut show_x,
|
||||||
mut show_y,
|
mut show_y,
|
||||||
custom_label_func,
|
label_formatter,
|
||||||
|
coordinates_formatter,
|
||||||
axis_formatters,
|
axis_formatters,
|
||||||
legend_config,
|
legend_config,
|
||||||
show_background,
|
show_background,
|
||||||
|
@ -630,7 +681,8 @@ impl Plot {
|
||||||
items,
|
items,
|
||||||
show_x,
|
show_x,
|
||||||
show_y,
|
show_y,
|
||||||
custom_label_func,
|
label_formatter,
|
||||||
|
coordinates_formatter,
|
||||||
axis_formatters,
|
axis_formatters,
|
||||||
show_axes,
|
show_axes,
|
||||||
transform: transform.clone(),
|
transform: transform.clone(),
|
||||||
|
@ -849,7 +901,8 @@ struct PreparedPlot {
|
||||||
items: Vec<Box<dyn PlotItem>>,
|
items: Vec<Box<dyn PlotItem>>,
|
||||||
show_x: bool,
|
show_x: bool,
|
||||||
show_y: bool,
|
show_y: bool,
|
||||||
custom_label_func: CustomLabelFuncRef,
|
label_formatter: LabelFormatter,
|
||||||
|
coordinates_formatter: Option<(Corner, CoordinatesFormatter)>,
|
||||||
axis_formatters: [AxisFormatter; 2],
|
axis_formatters: [AxisFormatter; 2],
|
||||||
show_axes: [bool; 2],
|
show_axes: [bool; 2],
|
||||||
transform: ScreenTransform,
|
transform: ScreenTransform,
|
||||||
|
@ -877,7 +930,24 @@ impl PreparedPlot {
|
||||||
self.hover(ui, pointer, &mut shapes);
|
self.hover(ui, pointer, &mut shapes);
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.painter().sub_region(*transform.frame()).extend(shapes);
|
let painter = ui.painter().sub_region(*transform.frame());
|
||||||
|
painter.extend(shapes);
|
||||||
|
|
||||||
|
if let Some((corner, formatter)) = self.coordinates_formatter.as_ref() {
|
||||||
|
if let Some(pointer) = response.hover_pos() {
|
||||||
|
let font_id = TextStyle::Monospace.resolve(ui.style());
|
||||||
|
let coordinate = transform.value_from_position(pointer);
|
||||||
|
let text = formatter.format(&coordinate, transform.bounds());
|
||||||
|
let padded_frame = transform.frame().shrink(4.0);
|
||||||
|
let (anchor, position) = match corner {
|
||||||
|
Corner::LeftTop => (Align2::LEFT_TOP, padded_frame.left_top()),
|
||||||
|
Corner::RightTop => (Align2::RIGHT_TOP, padded_frame.right_top()),
|
||||||
|
Corner::LeftBottom => (Align2::LEFT_BOTTOM, padded_frame.left_bottom()),
|
||||||
|
Corner::RightBottom => (Align2::RIGHT_BOTTOM, padded_frame.right_bottom()),
|
||||||
|
};
|
||||||
|
painter.text(position, anchor, text, font_id, ui.visuals().text_color());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint_axis(&self, ui: &Ui, axis: usize, shapes: &mut Vec<Shape>) {
|
fn paint_axis(&self, ui: &Ui, axis: usize, shapes: &mut Vec<Shape>) {
|
||||||
|
@ -888,6 +958,11 @@ impl PreparedPlot {
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
let bounds = transform.bounds();
|
let bounds = transform.bounds();
|
||||||
|
let axis_range = match axis {
|
||||||
|
0 => bounds.range_x(),
|
||||||
|
1 => bounds.range_y(),
|
||||||
|
_ => panic!("Axis {} does not exist.", axis),
|
||||||
|
};
|
||||||
|
|
||||||
let font_id = TextStyle::Body.resolve(ui.style());
|
let font_id = TextStyle::Body.resolve(ui.style());
|
||||||
|
|
||||||
|
@ -947,7 +1022,7 @@ impl PreparedPlot {
|
||||||
let color = color_from_alpha(ui, text_alpha);
|
let color = color_from_alpha(ui, text_alpha);
|
||||||
|
|
||||||
let text: String = if let Some(formatter) = axis_formatters[axis].as_deref() {
|
let text: String = if let Some(formatter) = axis_formatters[axis].as_deref() {
|
||||||
formatter(value_main)
|
formatter(value_main, &axis_range)
|
||||||
} else {
|
} else {
|
||||||
emath::round_to_decimals(value_main, 5).to_string() // hack
|
emath::round_to_decimals(value_main, 5).to_string() // hack
|
||||||
};
|
};
|
||||||
|
@ -982,7 +1057,7 @@ impl PreparedPlot {
|
||||||
transform,
|
transform,
|
||||||
show_x,
|
show_x,
|
||||||
show_y,
|
show_y,
|
||||||
custom_label_func,
|
label_formatter,
|
||||||
items,
|
items,
|
||||||
..
|
..
|
||||||
} = self;
|
} = self;
|
||||||
|
@ -1012,10 +1087,10 @@ impl PreparedPlot {
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some((item, elem)) = closest {
|
if let Some((item, elem)) = closest {
|
||||||
item.on_hover(elem, shapes, &plot, custom_label_func);
|
item.on_hover(elem, shapes, &plot, label_formatter);
|
||||||
} else {
|
} else {
|
||||||
let value = transform.value_from_position(pointer);
|
let value = transform.value_from_position(pointer);
|
||||||
items::rulers_at_value(pointer, value, "", &plot, shapes, custom_label_func);
|
items::rulers_at_value(pointer, value, "", &plot, shapes, label_formatter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -120,6 +120,10 @@ impl PlotBounds {
|
||||||
self.min[0]..=self.max[0]
|
self.min[0]..=self.max[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn range_y(&self) -> RangeInclusive<f64> {
|
||||||
|
self.min[1]..=self.max[1]
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn make_x_symmetrical(&mut self) {
|
pub(crate) fn make_x_symmetrical(&mut self) {
|
||||||
let x_abs = self.min[0].abs().max(self.max[0].abs());
|
let x_abs = self.min[0].abs().max(self.max[0].abs());
|
||||||
self.min[0] = -x_abs;
|
self.min[0] = -x_abs;
|
||||||
|
|
|
@ -2,8 +2,9 @@ use std::f64::consts::TAU;
|
||||||
|
|
||||||
use egui::*;
|
use egui::*;
|
||||||
use plot::{
|
use plot::{
|
||||||
Arrows, Bar, BarChart, BoxElem, BoxPlot, BoxSpread, Corner, HLine, Legend, Line, LineStyle,
|
Arrows, Bar, BarChart, BoxElem, BoxPlot, BoxSpread, CoordinatesFormatter, Corner, HLine,
|
||||||
MarkerShape, Plot, PlotImage, Points, Polygon, Text, VLine, Value, Values,
|
Legend, Line, LineStyle, MarkerShape, Plot, PlotImage, Points, Polygon, Text, VLine, Value,
|
||||||
|
Values,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq)]
|
||||||
|
@ -14,6 +15,7 @@ struct LineDemo {
|
||||||
circle_center: Pos2,
|
circle_center: Pos2,
|
||||||
square: bool,
|
square: bool,
|
||||||
proportional: bool,
|
proportional: bool,
|
||||||
|
coordinates: bool,
|
||||||
line_style: LineStyle,
|
line_style: LineStyle,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,6 +28,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,
|
||||||
|
coordinates: true,
|
||||||
line_style: LineStyle::Solid,
|
line_style: LineStyle::Solid,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,6 +44,7 @@ impl LineDemo {
|
||||||
square,
|
square,
|
||||||
proportional,
|
proportional,
|
||||||
line_style,
|
line_style,
|
||||||
|
coordinates,
|
||||||
..
|
..
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
|
@ -76,6 +80,8 @@ impl LineDemo {
|
||||||
.on_hover_text("Always keep the viewport square.");
|
.on_hover_text("Always keep the viewport square.");
|
||||||
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.checkbox(coordinates, "Show coordinates")
|
||||||
|
.on_hover_text("Can take a custom formatting function.");
|
||||||
|
|
||||||
ComboBox::from_label("Line style")
|
ComboBox::from_label("Line style")
|
||||||
.selected_text(line_style.to_string())
|
.selected_text(line_style.to_string())
|
||||||
|
@ -151,6 +157,9 @@ impl Widget for &mut LineDemo {
|
||||||
if self.proportional {
|
if self.proportional {
|
||||||
plot = plot.data_aspect(1.0);
|
plot = plot.data_aspect(1.0);
|
||||||
}
|
}
|
||||||
|
if self.coordinates {
|
||||||
|
plot = plot.coordinates_formatter(Corner::LeftBottom, CoordinatesFormatter::default());
|
||||||
|
}
|
||||||
plot.show(ui, |plot_ui| {
|
plot.show(ui, |plot_ui| {
|
||||||
plot_ui.line(self.circle());
|
plot_ui.line(self.circle());
|
||||||
plot_ui.line(self.sin());
|
plot_ui.line(self.sin());
|
||||||
|
@ -595,7 +604,7 @@ impl ChartsDemo {
|
||||||
.name("Set 4")
|
.name("Set 4")
|
||||||
.stack_on(&[&chart1, &chart2, &chart3]);
|
.stack_on(&[&chart1, &chart2, &chart3]);
|
||||||
|
|
||||||
let mut x_fmt: fn(f64) -> String = |val| {
|
let mut x_fmt: fn(f64, &std::ops::RangeInclusive<f64>) -> String = |val, _range| {
|
||||||
if val >= 0.0 && val <= 4.0 && is_approx_integer(val) {
|
if val >= 0.0 && val <= 4.0 && is_approx_integer(val) {
|
||||||
// Only label full days from 0 to 4
|
// Only label full days from 0 to 4
|
||||||
format!("Day {}", val)
|
format!("Day {}", val)
|
||||||
|
@ -605,7 +614,7 @@ impl ChartsDemo {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut y_fmt: fn(f64) -> String = |val| {
|
let mut y_fmt: fn(f64, &std::ops::RangeInclusive<f64>) -> String = |val, _range| {
|
||||||
let percent = 100.0 * val;
|
let percent = 100.0 * val;
|
||||||
|
|
||||||
if is_approx_integer(percent) && !is_approx_zero(percent) {
|
if is_approx_integer(percent) && !is_approx_zero(percent) {
|
||||||
|
|
Loading…
Reference in a new issue