mirror of
https://github.com/emilk/egui.git
synced 2026-09-03 07:10:04 -04:00
plot: Axis label API
This commit is contained in:
committed by
JohannesProgrammiert
parent
9d76be1131
commit
086bb49ede
@@ -14,88 +14,88 @@ use super::{transform::PlotTransform, GridMark, MIN_LINE_SPACING_IN_POINTS};
|
|||||||
|
|
||||||
pub(super) type AxisFormatterFn = fn(f64, usize, &RangeInclusive<f64>) -> String;
|
pub(super) type AxisFormatterFn = fn(f64, usize, &RangeInclusive<f64>) -> String;
|
||||||
|
|
||||||
/// Axis specifier.
|
/// Generic constant for x-Axis
|
||||||
///
|
pub(super) const X_AXIS: usize = 0;
|
||||||
/// Used to specify which kind of axis an [`AxisConfig`] refers to.
|
/// Generic constant for y-Axis
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
pub(super) const Y_AXIS: usize = 1;
|
||||||
pub enum Axis {
|
|
||||||
X = 0,
|
|
||||||
Y = 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Placement configuration for an axis.
|
/// Placement of an Axis.
|
||||||
///
|
///
|
||||||
/// `Default` means bottom for x, left for y.
|
/// `Default` means bottom for x-axis and left for y-axis.
|
||||||
/// `Opposite` means top for x, right for y.
|
/// `Opposite` means top for x-axis and right for y-axis.
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub enum AxisPlacement {
|
pub enum Placement {
|
||||||
Default,
|
Default,
|
||||||
Opposite,
|
Opposite,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// shorthand types for AxisHints, public API
|
||||||
|
/// Configuration for x-axis
|
||||||
|
pub type XAxisHints = AxisHints<X_AXIS>;
|
||||||
|
/// Configuration for y-axis
|
||||||
|
pub type YAxisHints = AxisHints<Y_AXIS>;
|
||||||
|
|
||||||
|
// shorthand types for AxisWidget
|
||||||
|
pub(super) type XAxisWidget = AxisWidget<X_AXIS>;
|
||||||
|
pub(super) type YAxisWidget = AxisWidget<Y_AXIS>;
|
||||||
/// Axis configuration.
|
/// Axis configuration.
|
||||||
///
|
///
|
||||||
/// Used to configure axis label and ticks.
|
/// Used to configure axis label and ticks.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct AxisConfig {
|
pub struct AxisHints<const AXIS: usize> {
|
||||||
pub(super) placement: AxisPlacement,
|
pub(super) label: String,
|
||||||
label: String,
|
|
||||||
pub(super) formatter: AxisFormatterFn,
|
pub(super) formatter: AxisFormatterFn,
|
||||||
digits: usize,
|
digits: usize,
|
||||||
pub(super) axis: Axis,
|
pub(super) placement: Placement,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for AxisConfig {
|
impl<const AXIS: usize> Debug for AxisHints<AXIS> {
|
||||||
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
|
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||||
|
let axis_str = match AXIS {
|
||||||
|
X_AXIS => "x-axis",
|
||||||
|
Y_AXIS => "y-axis",
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
write!(
|
write!(
|
||||||
fmt,
|
fmt,
|
||||||
"AxisConfig ( placement: {:?}, label: {}, formatter: ???, axis: {:?} )",
|
"Axis ( placement: {:?}, label: {}, formatter: ???, axis: {} )",
|
||||||
self.placement, self.label, self.axis
|
self.placement, self.label, axis_str
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO: this just a guess. It might cease to work if a user changes font size.
|
|
||||||
|
|
||||||
|
// TODO: this just a guess. It might cease to work if a user changes font size.
|
||||||
const LINE_HEIGHT: f32 = 12.0;
|
const LINE_HEIGHT: f32 = 12.0;
|
||||||
|
|
||||||
impl AxisConfig {
|
impl<const AXIS: usize> Default for AxisHints<AXIS> {
|
||||||
/// Initializes a default axis configuration for the specified [`Axis`].
|
/// Initializes a default axis configuration for the specified [`Axis`].
|
||||||
///
|
///
|
||||||
/// `placement` is bottom for x-axes and left for y-axes
|
/// `label` is 'x' or 'y'
|
||||||
/// `label` is empty
|
|
||||||
/// `formatter` is default float to string formatter
|
/// `formatter` is default float to string formatter
|
||||||
pub const fn default(axis: Axis) -> Self {
|
/// maximum `digits` on tick label is 5
|
||||||
|
fn default() -> Self {
|
||||||
|
let label = match AXIS {
|
||||||
|
X_AXIS => "x".to_string(),
|
||||||
|
Y_AXIS => "y".to_string(),
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
Self {
|
Self {
|
||||||
placement: AxisPlacement::Default,
|
label,
|
||||||
label: String::new(),
|
|
||||||
formatter: Self::default_formatter,
|
formatter: Self::default_formatter,
|
||||||
digits: 5,
|
digits: 5,
|
||||||
axis,
|
placement: Placement::Default,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Specify axis label
|
impl<const AXIS: usize> AxisHints<AXIS> {
|
||||||
pub fn label(mut self, label: String) -> Self {
|
|
||||||
self.label = label;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Specify custom formatter for ticks.
|
/// Specify custom formatter for ticks.
|
||||||
///
|
///
|
||||||
/// The first parameter of `formatter` is the raw tick value as `f64`.
|
/// The first parameter of `formatter` is the raw tick value as `f64`.
|
||||||
/// The second paramter is the maximum number of characters that fit into y-labels.
|
/// The second paramter is the maximum number of characters that fit into y-labels.
|
||||||
/// The second paramter of `formatter` is the currently shown range on this axis.
|
/// The second paramter of `formatter` is the currently shown range on this axis.
|
||||||
pub fn tick_formatter(
|
pub fn formatter(mut self, fmt: fn(f64, usize, &RangeInclusive<f64>) -> String) -> Self {
|
||||||
mut self,
|
self.formatter = fmt;
|
||||||
formatter: fn(f64, usize, &RangeInclusive<f64>) -> String,
|
|
||||||
) -> Self {
|
|
||||||
self.formatter = formatter;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Specify the placement for this axis.
|
|
||||||
pub fn placement(mut self, placement: AxisPlacement) -> Self {
|
|
||||||
self.placement = placement;
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,46 +111,65 @@ impl AxisConfig {
|
|||||||
format!("{}", tick_rounded)
|
format!("{}", tick_rounded)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Specify axis label.
|
||||||
|
///
|
||||||
|
/// The default is 'x' for x-axes and 'y' for y-axes.
|
||||||
|
pub fn label(mut self, label: String) -> Self {
|
||||||
|
self.label = label;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Specify maximum number of digits for ticks.
|
||||||
|
///
|
||||||
|
/// This is considered by the default tick formatter
|
||||||
|
/// and affects the width of the internal y-axis widget
|
||||||
pub fn max_digits(mut self, digits: usize) -> Self {
|
pub fn max_digits(mut self, digits: usize) -> Self {
|
||||||
self.digits = digits;
|
self.digits = digits;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Specify the placement of the axis.
|
||||||
|
pub fn placement(mut self, placement: Placement) -> Self {
|
||||||
|
self.placement = placement;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn thickness(&self) -> f32 {
|
pub(super) fn thickness(&self) -> f32 {
|
||||||
match self.axis {
|
match AXIS {
|
||||||
Axis::X => {
|
X_AXIS => {
|
||||||
if self.label.is_empty() {
|
if self.label.is_empty() {
|
||||||
1.0 * LINE_HEIGHT
|
1.0 * LINE_HEIGHT
|
||||||
} else {
|
} else {
|
||||||
3.0 * LINE_HEIGHT
|
3.0 * LINE_HEIGHT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Axis::Y => {
|
Y_AXIS => {
|
||||||
if self.label.is_empty() {
|
if self.label.is_empty() {
|
||||||
(self.digits as f32) * LINE_HEIGHT
|
(self.digits as f32) * LINE_HEIGHT
|
||||||
} else {
|
} else {
|
||||||
(self.digits as f32 + 1.0) * LINE_HEIGHT
|
(self.digits as f32 + 1.0) * LINE_HEIGHT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub(super) struct AxisWidget {
|
pub(super) struct AxisWidget<const AXIS: usize> {
|
||||||
pub(super) range: RangeInclusive<f64>,
|
pub(super) range: RangeInclusive<f64>,
|
||||||
pub(super) config: AxisConfig,
|
pub(super) hints: AxisHints<AXIS>,
|
||||||
pub(super) rect: Rect,
|
pub(super) rect: Rect,
|
||||||
pub(super) transform: Option<PlotTransform>,
|
pub(super) transform: Option<PlotTransform>,
|
||||||
pub(super) steps: Vec<GridMark>,
|
pub(super) steps: Vec<GridMark>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AxisWidget {
|
impl<const AXIS: usize> AxisWidget<AXIS> {
|
||||||
/// if `rect` as width or height == 0, is will be automatically calculated from ticks and text.
|
/// if `rect` as width or height == 0, is will be automatically calculated from ticks and text.
|
||||||
pub(super) fn new(config: AxisConfig, rect: Rect) -> Self {
|
pub(super) fn new(hints: AxisHints<AXIS>, rect: Rect) -> Self {
|
||||||
Self {
|
Self {
|
||||||
range: (0.0..=0.0),
|
range: (0.0..=0.0),
|
||||||
config,
|
hints,
|
||||||
rect,
|
rect,
|
||||||
transform: None,
|
transform: None,
|
||||||
steps: Vec::new(),
|
steps: Vec::new(),
|
||||||
@@ -158,54 +177,57 @@ impl AxisWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for AxisWidget {
|
impl<const AXIS: usize> Widget for AxisWidget<AXIS> {
|
||||||
fn ui(self, ui: &mut Ui) -> Response {
|
fn ui(self, ui: &mut Ui) -> Response {
|
||||||
// --- add label ---
|
// --- add label ---
|
||||||
let response = ui.allocate_rect(self.rect, Sense::click_and_drag());
|
let response = ui.allocate_rect(self.rect, Sense::click_and_drag());
|
||||||
if ui.is_rect_visible(response.rect) {
|
if ui.is_rect_visible(response.rect) {
|
||||||
let visuals = ui.style().visuals.clone();
|
let visuals = ui.style().visuals.clone();
|
||||||
let text: WidgetText = self.config.label.into();
|
let text: WidgetText = self.hints.label.into();
|
||||||
let galley = text.into_galley(ui, Some(false), f32::INFINITY, TextStyle::Body);
|
let galley = text.into_galley(ui, Some(false), f32::INFINITY, TextStyle::Body);
|
||||||
let text_color = visuals
|
let text_color = visuals
|
||||||
.override_text_color
|
.override_text_color
|
||||||
.unwrap_or(ui.visuals().text_color());
|
.unwrap_or(ui.visuals().text_color());
|
||||||
let angle: f32 = match self.config.axis {
|
let angle: f32 = match AXIS {
|
||||||
Axis::X => 0.0,
|
X_AXIS => 0.0,
|
||||||
Axis::Y => -std::f32::consts::PI * 0.5,
|
Y_AXIS => -std::f32::consts::PI * 0.5,
|
||||||
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
// select text_pos and angle depending on placement and orientation of widget
|
// select text_pos and angle depending on placement and orientation of widget
|
||||||
let text_pos = match self.config.placement {
|
let text_pos = match self.hints.placement {
|
||||||
AxisPlacement::Default => match self.config.axis {
|
Placement::Default => match AXIS {
|
||||||
Axis::X => {
|
X_AXIS => {
|
||||||
let pos = response.rect.center_bottom();
|
let pos = response.rect.center_bottom();
|
||||||
Pos2 {
|
Pos2 {
|
||||||
x: pos.x - galley.size().x / 2.0,
|
x: pos.x - galley.size().x / 2.0,
|
||||||
y: pos.y - galley.size().y * 1.25,
|
y: pos.y - galley.size().y * 1.25,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Axis::Y => {
|
Y_AXIS => {
|
||||||
let pos = response.rect.left_center();
|
let pos = response.rect.left_center();
|
||||||
Pos2 {
|
Pos2 {
|
||||||
x: pos.x,
|
x: pos.x,
|
||||||
y: pos.y + galley.size().x / 2.0,
|
y: pos.y + galley.size().x / 2.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
},
|
},
|
||||||
AxisPlacement::Opposite => match self.config.axis {
|
Placement::Opposite => match AXIS {
|
||||||
Axis::X => {
|
X_AXIS => {
|
||||||
let pos = response.rect.center_top();
|
let pos = response.rect.center_top();
|
||||||
Pos2 {
|
Pos2 {
|
||||||
x: pos.x - galley.size().x / 2.0,
|
x: pos.x - galley.size().x / 2.0,
|
||||||
y: pos.y + galley.size().y * 0.25,
|
y: pos.y + galley.size().y * 0.25,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Axis::Y => {
|
Y_AXIS => {
|
||||||
let pos = response.rect.right_center();
|
let pos = response.rect.right_center();
|
||||||
Pos2 {
|
Pos2 {
|
||||||
x: pos.x - galley.size().y * 1.5,
|
x: pos.x - galley.size().y * 1.5,
|
||||||
y: pos.y + galley.size().x / 2.0,
|
y: pos.y + galley.size().x / 2.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
let shape = TextShape {
|
let shape = TextShape {
|
||||||
@@ -225,11 +247,10 @@ impl Widget for AxisWidget {
|
|||||||
};
|
};
|
||||||
|
|
||||||
for step in self.steps {
|
for step in self.steps {
|
||||||
let text = (self.config.formatter)(step.value, self.config.digits, &self.range);
|
let text = (self.hints.formatter)(step.value, self.hints.digits, &self.range);
|
||||||
if !text.is_empty() {
|
if !text.is_empty() {
|
||||||
let spacing_in_points = (transform.dpos_dvalue()[self.config.axis as usize]
|
let spacing_in_points =
|
||||||
* step.step_size)
|
(transform.dpos_dvalue()[AXIS] * step.step_size).abs() as f32;
|
||||||
.abs() as f32;
|
|
||||||
|
|
||||||
let line_alpha = remap_clamp(
|
let line_alpha = remap_clamp(
|
||||||
spacing_in_points,
|
spacing_in_points,
|
||||||
@@ -242,11 +263,11 @@ impl Widget for AxisWidget {
|
|||||||
let galley = ui
|
let galley = ui
|
||||||
.painter()
|
.painter()
|
||||||
.layout_no_wrap(text, font_id.clone(), line_color);
|
.layout_no_wrap(text, font_id.clone(), line_color);
|
||||||
let text_pos = match self.config.axis {
|
let text_pos = match AXIS {
|
||||||
Axis::X => {
|
X_AXIS => {
|
||||||
let y = match self.config.placement {
|
let y = match self.hints.placement {
|
||||||
AxisPlacement::Default => self.rect.min.y,
|
Placement::Default => self.rect.min.y,
|
||||||
AxisPlacement::Opposite => self.rect.max.y - galley.size().y,
|
Placement::Opposite => self.rect.max.y - galley.size().y,
|
||||||
};
|
};
|
||||||
let projected_point = super::PlotPoint::new(step.value, 0.0);
|
let projected_point = super::PlotPoint::new(step.value, 0.0);
|
||||||
Pos2 {
|
Pos2 {
|
||||||
@@ -255,10 +276,10 @@ impl Widget for AxisWidget {
|
|||||||
y,
|
y,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Axis::Y => {
|
Y_AXIS => {
|
||||||
let x = match self.config.placement {
|
let x = match self.hints.placement {
|
||||||
AxisPlacement::Default => self.rect.max.x - galley.size().x,
|
Placement::Default => self.rect.max.x - galley.size().x,
|
||||||
AxisPlacement::Opposite => self.rect.min.x,
|
Placement::Opposite => self.rect.min.x,
|
||||||
};
|
};
|
||||||
let projected_point = super::PlotPoint::new(0.0, step.value);
|
let projected_point = super::PlotPoint::new(0.0, step.value);
|
||||||
Pos2 {
|
Pos2 {
|
||||||
@@ -267,6 +288,7 @@ impl Widget for AxisWidget {
|
|||||||
- galley.size().y / 2.0,
|
- galley.size().y / 2.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
ui.painter().add(Shape::galley(text_pos, galley));
|
ui.painter().add(Shape::galley(text_pos, galley));
|
||||||
|
|||||||
29
crates/egui/src/widgets/plot/memory.rs
Normal file
29
crates/egui/src/widgets/plot/memory.rs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
use epaint::Pos2;
|
||||||
|
|
||||||
|
use crate::{Id, Context};
|
||||||
|
|
||||||
|
use super::{AxisBools, transform::ScreenTransform};
|
||||||
|
|
||||||
|
/// Information about the plot that has to persist between frames.
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(super) struct PlotMemory {
|
||||||
|
/// Indicates if the user has modified the bounds, for example by moving or zooming,
|
||||||
|
/// or if the bounds should be calculated based by included point or auto bounds.
|
||||||
|
pub(super) bounds_modified: AxisBools,
|
||||||
|
pub(super) hovered_entry: Option<String>,
|
||||||
|
pub(super) hidden_items: ahash::HashSet<String>,
|
||||||
|
pub(super) last_screen_transform: ScreenTransform,
|
||||||
|
/// Allows to remember the first click position when performing a boxed zoom
|
||||||
|
pub(super) last_click_pos_for_zoom: Option<Pos2>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PlotMemory {
|
||||||
|
pub fn load(ctx: &Context, id: Id) -> Option<Self> {
|
||||||
|
ctx.data().get_persisted(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn store(self, ctx: &Context, id: Id) {
|
||||||
|
ctx.data().insert_persisted(id, self);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
//! Simple plotting library.
|
//! Simple plotting library.
|
||||||
|
|
||||||
use ahash::HashMap;
|
use ahash::HashMap;
|
||||||
|
use std::ops::RangeInclusive;
|
||||||
|
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use epaint::util::FloatOrd;
|
use epaint::util::FloatOrd;
|
||||||
use epaint::Hsva;
|
use epaint::Hsva;
|
||||||
|
|
||||||
pub use axis::{AxisPlacement, Axis, AxisConfig};
|
use axis::{XAxisWidget, YAxisWidget, X_AXIS, Y_AXIS};
|
||||||
use axis::AxisWidget;
|
|
||||||
use items::PlotItem;
|
use items::PlotItem;
|
||||||
use legend::LegendWidget;
|
use legend::LegendWidget;
|
||||||
|
|
||||||
@@ -20,7 +20,9 @@ pub use transform::{PlotBounds, PlotTransform};
|
|||||||
|
|
||||||
use items::{horizontal_line, rulers_color, vertical_line};
|
use items::{horizontal_line, rulers_color, vertical_line};
|
||||||
|
|
||||||
pub mod axis;
|
pub use axis::{Placement, XAxisHints, YAxisHints};
|
||||||
|
|
||||||
|
mod axis;
|
||||||
mod items;
|
mod items;
|
||||||
mod legend;
|
mod legend;
|
||||||
mod transform;
|
mod transform;
|
||||||
@@ -180,8 +182,7 @@ pub struct PlotResponse<R> {
|
|||||||
pub struct Plot {
|
pub struct Plot {
|
||||||
id_source: Id,
|
id_source: Id,
|
||||||
|
|
||||||
center_x_axis: bool,
|
center_axis: AxisBools,
|
||||||
center_y_axis: bool,
|
|
||||||
allow_zoom: AxisBools,
|
allow_zoom: AxisBools,
|
||||||
allow_drag: AxisBools,
|
allow_drag: AxisBools,
|
||||||
allow_scroll: bool,
|
allow_scroll: bool,
|
||||||
@@ -206,11 +207,12 @@ pub struct Plot {
|
|||||||
show_y: bool,
|
show_y: bool,
|
||||||
label_formatter: LabelFormatter,
|
label_formatter: LabelFormatter,
|
||||||
coordinates_formatter: Option<(Corner, CoordinatesFormatter)>,
|
coordinates_formatter: Option<(Corner, CoordinatesFormatter)>,
|
||||||
axis_config: Vec<AxisConfig>,
|
x_axes: Vec<XAxisHints>, // default x axes
|
||||||
|
y_axes: Vec<YAxisHints>, // default y axes
|
||||||
legend_config: Option<Legend>,
|
legend_config: Option<Legend>,
|
||||||
show_background: bool,
|
show_background: bool,
|
||||||
show_axes: [bool; 2],
|
show_axes: AxisBools,
|
||||||
|
show_grid: AxisBools,
|
||||||
grid_spacers: [GridSpacer; 2],
|
grid_spacers: [GridSpacer; 2],
|
||||||
sharp_grid_lines: bool,
|
sharp_grid_lines: bool,
|
||||||
clamp_grid: bool,
|
clamp_grid: bool,
|
||||||
@@ -222,8 +224,7 @@ impl Plot {
|
|||||||
Self {
|
Self {
|
||||||
id_source: Id::new(id_source),
|
id_source: Id::new(id_source),
|
||||||
|
|
||||||
center_x_axis: false,
|
center_axis: false.into(),
|
||||||
center_y_axis: false,
|
|
||||||
allow_zoom: true.into(),
|
allow_zoom: true.into(),
|
||||||
allow_drag: true.into(),
|
allow_drag: true.into(),
|
||||||
allow_scroll: true,
|
allow_scroll: true,
|
||||||
@@ -248,11 +249,12 @@ impl Plot {
|
|||||||
show_y: true,
|
show_y: true,
|
||||||
label_formatter: None,
|
label_formatter: None,
|
||||||
coordinates_formatter: None,
|
coordinates_formatter: None,
|
||||||
axis_config: vec![AxisConfig::default(Axis::X), AxisConfig::default(Axis::Y)],
|
x_axes: vec![XAxisHints::default()],
|
||||||
|
y_axes: vec![YAxisHints::default()],
|
||||||
legend_config: None,
|
legend_config: None,
|
||||||
show_background: true,
|
show_background: true,
|
||||||
show_axes: [true; 2],
|
show_axes: true.into(),
|
||||||
|
show_grid: true.into(),
|
||||||
grid_spacers: [log_grid_spacer(10), log_grid_spacer(10)],
|
grid_spacers: [log_grid_spacer(10), log_grid_spacer(10)],
|
||||||
sharp_grid_lines: true,
|
sharp_grid_lines: true,
|
||||||
clamp_grid: false,
|
clamp_grid: false,
|
||||||
@@ -311,13 +313,13 @@ impl Plot {
|
|||||||
|
|
||||||
/// Always keep the x-axis centered. Default: `false`.
|
/// Always keep the x-axis centered. Default: `false`.
|
||||||
pub fn center_x_axis(mut self, on: bool) -> Self {
|
pub fn center_x_axis(mut self, on: bool) -> Self {
|
||||||
self.center_x_axis = on;
|
self.center_axis.x = on;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Always keep the y-axis centered. Default: `false`.
|
/// Always keep the y-axis centered. Default: `false`.
|
||||||
pub fn center_y_axis(mut self, on: bool) -> Self {
|
pub fn center_y_axis(mut self, on: bool) -> Self {
|
||||||
self.center_y_axis = on;
|
self.center_axis.y = on;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -506,11 +508,21 @@ impl Plot {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Show the axes.
|
/// Show axis labels.
|
||||||
/// Can be useful to disable if the plot is overlaid over an existing grid or content.
|
///
|
||||||
/// Default: `[true; 2]`.
|
/// Default: `[true; 2]`.
|
||||||
pub fn show_axes(mut self, show: [bool; 2]) -> Self {
|
pub fn show_axes(mut self, show: [bool; 2]) -> Self {
|
||||||
self.show_axes = show;
|
self.show_axes.x = show[0];
|
||||||
|
self.show_axes.y = show[1];
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Show the grid.
|
||||||
|
/// Can be useful to disable if the plot is overlaid over an existing grid or content.
|
||||||
|
/// Default: `[true; 2]`.
|
||||||
|
pub fn show_grid(mut self, show: [bool; 2]) -> Self {
|
||||||
|
self.show_grid.x = show[0];
|
||||||
|
self.show_grid.y = show[1];
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -553,12 +565,74 @@ impl Plot {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configure Axes.
|
/// Set the x axis label of the bottom x-axis
|
||||||
|
pub fn x_axis_label(mut self, label: String) -> Self {
|
||||||
|
if !self.x_axes.is_empty() {
|
||||||
|
self.x_axes[0].label = label;
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Set the y axis label of the left y-axis
|
||||||
|
pub fn y_axis_label(mut self, label: String) -> Self {
|
||||||
|
if !self.y_axes.is_empty() {
|
||||||
|
self.y_axes[0].label = label;
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the x-axis position
|
||||||
|
pub fn x_axis_position(mut self, placement: axis::Placement) -> Self {
|
||||||
|
if !self.x_axes.is_empty() {
|
||||||
|
self.x_axes[0].placement = placement;
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the y-axis position
|
||||||
|
pub fn y_axis_position(mut self, placement: axis::Placement) -> Self {
|
||||||
|
if !self.y_axes.is_empty() {
|
||||||
|
self.y_axes[0].placement = placement;
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Specify custom formatter for ticks on x-axis
|
||||||
///
|
///
|
||||||
/// Takes a vector of [`AxisConfig`] objects as argument to configure the plot axes.
|
/// The first parameter of `fmt` is the raw tick value as `f64`.
|
||||||
/// See [`AxisConfig`] for available options.
|
/// The second paramter is the maximum requested number of characters per tick label.
|
||||||
pub fn axes(mut self, axis_config: Vec<AxisConfig>) -> Self {
|
/// The second paramter of `fmt` is the currently shown range on this axis.
|
||||||
self.axis_config = axis_config;
|
pub fn x_axis_formatter(mut self, fmt: fn(f64, usize, &RangeInclusive<f64>) -> String) -> Self {
|
||||||
|
if !self.x_axes.is_empty() {
|
||||||
|
self.x_axes[0].formatter = fmt;
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Specify custom formatter for ticks on y-axis
|
||||||
|
///
|
||||||
|
/// The first parameter of `formatter` is the raw tick value as `f64`.
|
||||||
|
/// The second paramter is the maximum requested number of characters per tick label.
|
||||||
|
/// The second paramter of `formatter` is the currently shown range on this axis.
|
||||||
|
pub fn y_axis_formatter(mut self, fmt: fn(f64, usize, &RangeInclusive<f64>) -> String) -> Self {
|
||||||
|
if !self.y_axes.is_empty() {
|
||||||
|
self.y_axes[0].formatter = fmt;
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set custom configuration for bottom x-axis
|
||||||
|
///
|
||||||
|
/// More than one axis may be specified.
|
||||||
|
pub fn custom_x_axes(mut self, hints: Vec<XAxisHints>) -> Self {
|
||||||
|
self.x_axes = hints;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set custom configuration for left y-axis
|
||||||
|
///
|
||||||
|
/// More than one axis may be specified.
|
||||||
|
pub fn custom_y_axes(mut self, hints: Vec<YAxisHints>) -> Self {
|
||||||
|
self.y_axes = hints;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -574,8 +648,7 @@ impl Plot {
|
|||||||
) -> PlotResponse<R> {
|
) -> PlotResponse<R> {
|
||||||
let Self {
|
let Self {
|
||||||
id_source,
|
id_source,
|
||||||
center_x_axis,
|
center_axis,
|
||||||
center_y_axis,
|
|
||||||
allow_zoom,
|
allow_zoom,
|
||||||
allow_drag,
|
allow_drag,
|
||||||
allow_scroll,
|
allow_scroll,
|
||||||
@@ -594,11 +667,13 @@ impl Plot {
|
|||||||
mut show_y,
|
mut show_y,
|
||||||
label_formatter,
|
label_formatter,
|
||||||
coordinates_formatter,
|
coordinates_formatter,
|
||||||
axis_config,
|
x_axes,
|
||||||
|
y_axes,
|
||||||
legend_config,
|
legend_config,
|
||||||
reset,
|
reset,
|
||||||
show_background,
|
show_background,
|
||||||
show_axes,
|
show_axes,
|
||||||
|
show_grid,
|
||||||
linked_axes,
|
linked_axes,
|
||||||
linked_cursors,
|
linked_cursors,
|
||||||
|
|
||||||
@@ -637,7 +712,6 @@ impl Plot {
|
|||||||
min: pos,
|
min: pos,
|
||||||
max: pos + size,
|
max: pos + size,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Next we want to create this layout.
|
// Next we want to create this layout.
|
||||||
// Incides are only examples.
|
// Incides are only examples.
|
||||||
//
|
//
|
||||||
@@ -660,41 +734,47 @@ impl Plot {
|
|||||||
// + +--------------------+-d-+
|
// + +--------------------+-d-+
|
||||||
//
|
//
|
||||||
|
|
||||||
let mut axis_widgets = Vec::<AxisWidget>::new();
|
let mut plot_rect: Rect = {
|
||||||
let plot_rect: Rect;
|
|
||||||
{
|
|
||||||
// find dimensions of axis labels
|
// find dimensions of axis labels
|
||||||
// for a, b, c, d meanings see picture
|
// for a, b, c, d meanings see picture
|
||||||
let mut a = 0.0;
|
let mut a = 0.0;
|
||||||
let mut b = 0.0;
|
let mut b = 0.0;
|
||||||
let mut c = 0.0;
|
let mut c = 0.0;
|
||||||
let mut d = 0.0;
|
let mut d = 0.0;
|
||||||
for cfg in &axis_config {
|
if show_axes.x {
|
||||||
match cfg.placement {
|
for cfg in &x_axes {
|
||||||
AxisPlacement::Default => match cfg.axis {
|
match cfg.placement {
|
||||||
Axis::X => {
|
axis::Placement::Default => {
|
||||||
a += cfg.thickness();
|
a += cfg.thickness();
|
||||||
}
|
}
|
||||||
Axis::Y => {
|
axis::Placement::Opposite => {
|
||||||
b += cfg.thickness();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
AxisPlacement::Opposite => match cfg.axis {
|
|
||||||
Axis::X => {
|
|
||||||
c += cfg.thickness();
|
c += cfg.thickness();
|
||||||
}
|
}
|
||||||
Axis::Y => {
|
}
|
||||||
d += cfg.thickness();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if show_axes.y {
|
||||||
|
for cfg in &y_axes {
|
||||||
|
match cfg.placement {
|
||||||
|
axis::Placement::Default => {
|
||||||
|
b += cfg.thickness();
|
||||||
|
}
|
||||||
|
axis::Placement::Opposite => {
|
||||||
|
d += cfg.thickness();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// determine plot rectangle
|
// determine plot rectangle
|
||||||
plot_rect = Rect {
|
Rect {
|
||||||
min: complete_rect.min + Vec2::new(b, c),
|
min: complete_rect.min + Vec2::new(b, c),
|
||||||
max: complete_rect.max - Vec2::new(d, a),
|
max: complete_rect.max - Vec2::new(d, a),
|
||||||
};
|
}
|
||||||
|
};
|
||||||
|
let mut x_axis_widgets = Vec::<XAxisWidget>::new();
|
||||||
|
let mut y_axis_widgets = Vec::<YAxisWidget>::new();
|
||||||
|
{
|
||||||
// determine absolute rectangle for each axis label widget
|
// determine absolute rectangle for each axis label widget
|
||||||
// widget cnt per border of plot in order left, top, right, bottom
|
// widget cnt per border of plot in order left, top, right, bottom
|
||||||
struct WidgetCnt {
|
struct WidgetCnt {
|
||||||
@@ -709,18 +789,11 @@ impl Plot {
|
|||||||
right: 0,
|
right: 0,
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
};
|
};
|
||||||
for cfg in &axis_config {
|
if show_axes.x {
|
||||||
let size_x = Vec2 {
|
for cfg in &x_axes {
|
||||||
x: cfg.thickness(),
|
let size_y = Vec2::new(0.0, cfg.thickness());
|
||||||
y: 0.0,
|
let rect = match cfg.placement {
|
||||||
};
|
axis::Placement::Default => {
|
||||||
let size_y = Vec2 {
|
|
||||||
x: 0.0,
|
|
||||||
y: cfg.thickness(),
|
|
||||||
};
|
|
||||||
let rect: Rect = match cfg.placement {
|
|
||||||
AxisPlacement::Default => match cfg.axis {
|
|
||||||
Axis::X => {
|
|
||||||
let off = widget_cnt.bottom as f32;
|
let off = widget_cnt.bottom as f32;
|
||||||
widget_cnt.bottom += 1;
|
widget_cnt.bottom += 1;
|
||||||
Rect {
|
Rect {
|
||||||
@@ -728,17 +801,7 @@ impl Plot {
|
|||||||
max: plot_rect.right_bottom() + size_y * (off + 1.0),
|
max: plot_rect.right_bottom() + size_y * (off + 1.0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Axis::Y => {
|
axis::Placement::Opposite => {
|
||||||
let off = widget_cnt.left as f32;
|
|
||||||
widget_cnt.left += 1;
|
|
||||||
Rect {
|
|
||||||
min: plot_rect.left_top() - size_x * (off + 1.0),
|
|
||||||
max: plot_rect.left_bottom() - size_x * off,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
AxisPlacement::Opposite => match cfg.axis {
|
|
||||||
Axis::X => {
|
|
||||||
let off = widget_cnt.top as f32;
|
let off = widget_cnt.top as f32;
|
||||||
widget_cnt.top += 1;
|
widget_cnt.top += 1;
|
||||||
Rect {
|
Rect {
|
||||||
@@ -746,7 +809,23 @@ impl Plot {
|
|||||||
max: plot_rect.right_top() - size_y * off,
|
max: plot_rect.right_top() - size_y * off,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Axis::Y => {
|
};
|
||||||
|
x_axis_widgets.push(XAxisWidget::new(cfg.clone(), rect));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if show_axes.y {
|
||||||
|
for cfg in &y_axes {
|
||||||
|
let size_x = Vec2::new(cfg.thickness(), 0.0);
|
||||||
|
let rect = match cfg.placement {
|
||||||
|
axis::Placement::Default => {
|
||||||
|
let off = widget_cnt.left as f32;
|
||||||
|
widget_cnt.left += 1;
|
||||||
|
Rect {
|
||||||
|
min: plot_rect.left_top() - size_x * (off + 1.0),
|
||||||
|
max: plot_rect.left_bottom() - size_x * off,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
axis::Placement::Opposite => {
|
||||||
let off = widget_cnt.right as f32;
|
let off = widget_cnt.right as f32;
|
||||||
widget_cnt.right += 1;
|
widget_cnt.right += 1;
|
||||||
Rect {
|
Rect {
|
||||||
@@ -754,14 +833,20 @@ impl Plot {
|
|||||||
max: plot_rect.right_bottom() + size_x * (off + 1.0),
|
max: plot_rect.right_bottom() + size_x * (off + 1.0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
};
|
y_axis_widgets.push(YAxisWidget::new(cfg.clone(), rect));
|
||||||
axis_widgets.push(AxisWidget::new(cfg.clone(), rect));
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if to little space, remove axis widgets
|
||||||
|
if plot_rect.width() <= 0.0 || plot_rect.height() <= 0.0 {
|
||||||
|
y_axis_widgets.clear();
|
||||||
|
x_axis_widgets.clear();
|
||||||
|
plot_rect = complete_rect;
|
||||||
|
}
|
||||||
|
|
||||||
// Allocate the plot window.
|
// Allocate the plot window.
|
||||||
// let (rect, response) = ui.allocate_exact_size(size, Sense::drag());
|
|
||||||
let response = ui.allocate_rect(plot_rect, Sense::drag());
|
let response = ui.allocate_rect(plot_rect, Sense::drag());
|
||||||
let rect = plot_rect;
|
let rect = plot_rect;
|
||||||
// Load or initialize the memory.
|
// Load or initialize the memory.
|
||||||
@@ -786,8 +871,8 @@ impl Plot {
|
|||||||
last_plot_transform: PlotTransform::new(
|
last_plot_transform: PlotTransform::new(
|
||||||
rect,
|
rect,
|
||||||
min_auto_bounds,
|
min_auto_bounds,
|
||||||
center_x_axis,
|
center_axis.x,
|
||||||
center_y_axis,
|
center_axis.y,
|
||||||
),
|
),
|
||||||
last_click_pos_for_zoom: None,
|
last_click_pos_for_zoom: None,
|
||||||
});
|
});
|
||||||
@@ -948,7 +1033,7 @@ impl Plot {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut transform = PlotTransform::new(rect, bounds, center_x_axis, center_y_axis);
|
let mut transform = PlotTransform::new(rect, bounds, center_axis.x, center_axis.y);
|
||||||
|
|
||||||
// Enforce aspect ratio
|
// Enforce aspect ratio
|
||||||
if let Some(data_aspect) = data_aspect {
|
if let Some(data_aspect) = data_aspect {
|
||||||
@@ -1056,23 +1141,36 @@ impl Plot {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for mut widget in axis_widgets {
|
// --- transform initialized
|
||||||
let axis = widget.config.axis;
|
|
||||||
let bounds = transform.bounds();
|
// Add legend widgets to plot
|
||||||
let axis_range = match axis {
|
let bounds = transform.bounds();
|
||||||
Axis::X => bounds.range_x(),
|
let x_axis_range = bounds.range_x();
|
||||||
Axis::Y => bounds.range_y(),
|
let x_steps = {
|
||||||
};
|
|
||||||
widget.range = axis_range;
|
|
||||||
let input = GridInput {
|
let input = GridInput {
|
||||||
bounds: (bounds.min[axis as usize], bounds.max[axis as usize]),
|
bounds: (bounds.min[X_AXIS], bounds.max[X_AXIS]),
|
||||||
base_step_size: transform.dvalue_dpos()[axis as usize]
|
base_step_size: transform.dvalue_dpos()[X_AXIS] * MIN_LINE_SPACING_IN_POINTS * 2.0,
|
||||||
* MIN_LINE_SPACING_IN_POINTS
|
|
||||||
* 2.0,
|
|
||||||
};
|
};
|
||||||
let steps = (grid_spacers[axis as usize])(input);
|
(grid_spacers[X_AXIS])(input)
|
||||||
|
};
|
||||||
|
let y_axis_range = bounds.range_y();
|
||||||
|
let y_steps = {
|
||||||
|
let input = GridInput {
|
||||||
|
bounds: (bounds.min[Y_AXIS], bounds.max[Y_AXIS]),
|
||||||
|
base_step_size: transform.dvalue_dpos()[Y_AXIS] * MIN_LINE_SPACING_IN_POINTS * 2.0,
|
||||||
|
};
|
||||||
|
(grid_spacers[Y_AXIS])(input)
|
||||||
|
};
|
||||||
|
for mut widget in x_axis_widgets {
|
||||||
|
widget.range = x_axis_range.clone();
|
||||||
widget.transform = Some(transform.clone());
|
widget.transform = Some(transform.clone());
|
||||||
widget.steps = steps;
|
widget.steps = x_steps.clone();
|
||||||
|
ui.add(widget);
|
||||||
|
}
|
||||||
|
for mut widget in y_axis_widgets {
|
||||||
|
widget.range = y_axis_range.clone();
|
||||||
|
widget.transform = Some(transform.clone());
|
||||||
|
widget.steps = y_steps.clone();
|
||||||
ui.add(widget);
|
ui.add(widget);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1087,11 +1185,10 @@ impl Plot {
|
|||||||
show_y,
|
show_y,
|
||||||
label_formatter,
|
label_formatter,
|
||||||
coordinates_formatter,
|
coordinates_formatter,
|
||||||
// axis_config,
|
show_grid,
|
||||||
show_axes,
|
transform: transform.clone(),
|
||||||
transform,
|
draw_cursor_x: linked_cursors.as_ref().map_or(false, |group| group.1.x),
|
||||||
draw_cursor_x: linked_cursors.as_ref().map_or(false, |(_, group)| group.x),
|
draw_cursor_y: linked_cursors.as_ref().map_or(false, |group| group.1.y),
|
||||||
draw_cursor_y: linked_cursors.as_ref().map_or(false, |(_, group)| group.y),
|
|
||||||
draw_cursors,
|
draw_cursors,
|
||||||
grid_spacers,
|
grid_spacers,
|
||||||
sharp_grid_lines,
|
sharp_grid_lines,
|
||||||
@@ -1451,13 +1548,13 @@ struct PreparedPlot {
|
|||||||
label_formatter: LabelFormatter,
|
label_formatter: LabelFormatter,
|
||||||
coordinates_formatter: Option<(Corner, CoordinatesFormatter)>,
|
coordinates_formatter: Option<(Corner, CoordinatesFormatter)>,
|
||||||
// axis_formatters: [AxisFormatter; 2],
|
// axis_formatters: [AxisFormatter; 2],
|
||||||
show_axes: [bool; 2],
|
|
||||||
transform: PlotTransform,
|
transform: PlotTransform,
|
||||||
|
show_grid: AxisBools,
|
||||||
|
grid_spacers: [GridSpacer; 2],
|
||||||
draw_cursor_x: bool,
|
draw_cursor_x: bool,
|
||||||
draw_cursor_y: bool,
|
draw_cursor_y: bool,
|
||||||
draw_cursors: Vec<Cursor>,
|
draw_cursors: Vec<Cursor>,
|
||||||
|
|
||||||
grid_spacers: [GridSpacer; 2],
|
|
||||||
sharp_grid_lines: bool,
|
sharp_grid_lines: bool,
|
||||||
clamp_grid: bool,
|
clamp_grid: bool,
|
||||||
}
|
}
|
||||||
@@ -1466,11 +1563,11 @@ impl PreparedPlot {
|
|||||||
fn ui(self, ui: &mut Ui, response: &Response) -> Vec<Cursor> {
|
fn ui(self, ui: &mut Ui, response: &Response) -> Vec<Cursor> {
|
||||||
let mut axes_shapes = Vec::new();
|
let mut axes_shapes = Vec::new();
|
||||||
|
|
||||||
if self.show_axes[Axis::X as usize] {
|
if self.show_grid.x {
|
||||||
self.paint_axis(ui, Axis::X, &mut axes_shapes, self.sharp_grid_lines);
|
self.paint_grid::<{ X_AXIS }>(ui, &mut axes_shapes);
|
||||||
}
|
}
|
||||||
if self.show_axes[Axis::Y as usize] {
|
if self.show_grid.y {
|
||||||
self.paint_axis(ui, Axis::Y, &mut axes_shapes, self.sharp_grid_lines);
|
self.paint_grid::<{ Y_AXIS }>(ui, &mut axes_shapes);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort the axes by strength so that those with higher strength are drawn in front.
|
// Sort the axes by strength so that those with higher strength are drawn in front.
|
||||||
@@ -1547,14 +1644,7 @@ impl PreparedPlot {
|
|||||||
cursors
|
cursors
|
||||||
}
|
}
|
||||||
|
|
||||||
// `axis`=0 means x-axis, `axis`=1 means y-axis.
|
fn paint_grid<const AXIS: usize>(&self, ui: &Ui, shapes: &mut Vec<(Shape, f32)>) {
|
||||||
fn paint_axis(
|
|
||||||
&self,
|
|
||||||
ui: &Ui,
|
|
||||||
axis: Axis,
|
|
||||||
shapes: &mut Vec<(Shape, f32)>,
|
|
||||||
sharp_grid_lines: bool,
|
|
||||||
) {
|
|
||||||
#![allow(clippy::collapsible_else_if)]
|
#![allow(clippy::collapsible_else_if)]
|
||||||
let Self {
|
let Self {
|
||||||
transform,
|
transform,
|
||||||
@@ -1566,14 +1656,13 @@ impl PreparedPlot {
|
|||||||
|
|
||||||
// Where on the cross-dimension to show the label values
|
// Where on the cross-dimension to show the label values
|
||||||
let bounds = transform.bounds();
|
let bounds = transform.bounds();
|
||||||
let value_cross =
|
let value_cross = 0.0_f64.clamp(bounds.min[1 - AXIS], bounds.max[1 - AXIS]);
|
||||||
0.0_f64.clamp(bounds.min[1 - axis as usize], bounds.max[1 - axis as usize]);
|
|
||||||
|
|
||||||
let input = GridInput {
|
let input = GridInput {
|
||||||
bounds: (bounds.min[axis as usize], bounds.max[axis as usize]),
|
bounds: (bounds.min[AXIS], bounds.max[AXIS]),
|
||||||
base_step_size: transform.dvalue_dpos()[axis as usize] * MIN_LINE_SPACING_IN_POINTS,
|
base_step_size: transform.dvalue_dpos()[AXIS] * MIN_LINE_SPACING_IN_POINTS,
|
||||||
};
|
};
|
||||||
let steps = (grid_spacers[axis as usize])(input);
|
let steps = (grid_spacers[AXIS])(input);
|
||||||
|
|
||||||
let clamp_range = clamp_grid.then(|| {
|
let clamp_range = clamp_grid.then(|| {
|
||||||
let mut tight_bounds = PlotBounds::NOTHING;
|
let mut tight_bounds = PlotBounds::NOTHING;
|
||||||
@@ -1589,7 +1678,7 @@ impl PreparedPlot {
|
|||||||
let value_main = step.value;
|
let value_main = step.value;
|
||||||
|
|
||||||
if let Some(clamp_range) = clamp_range {
|
if let Some(clamp_range) = clamp_range {
|
||||||
if axis == Axis::X {
|
if AXIS == X_AXIS {
|
||||||
if !clamp_range.range_x().contains(&value_main) {
|
if !clamp_range.range_x().contains(&value_main) {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
@@ -1600,14 +1689,14 @@ impl PreparedPlot {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let value = match axis {
|
let value = match AXIS {
|
||||||
Axis::X => PlotPoint::new(value_main, value_cross),
|
X_AXIS => PlotPoint::new(value_main, value_cross),
|
||||||
Axis::Y => PlotPoint::new(value_cross, value_main),
|
Y_AXIS => PlotPoint::new(value_cross, value_main),
|
||||||
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let pos_in_gui = transform.position_from_point(&value);
|
let pos_in_gui = transform.position_from_point(&value);
|
||||||
let spacing_in_points =
|
let spacing_in_points = (transform.dpos_dvalue()[AXIS] * step.step_size).abs() as f32;
|
||||||
(transform.dpos_dvalue()[axis as usize] * step.step_size).abs() as f32;
|
|
||||||
|
|
||||||
if spacing_in_points > MIN_LINE_SPACING_IN_POINTS as f32 {
|
if spacing_in_points > MIN_LINE_SPACING_IN_POINTS as f32 {
|
||||||
let line_strength = remap_clamp(
|
let line_strength = remap_clamp(
|
||||||
@@ -1620,11 +1709,11 @@ impl PreparedPlot {
|
|||||||
|
|
||||||
let mut p0 = pos_in_gui;
|
let mut p0 = pos_in_gui;
|
||||||
let mut p1 = pos_in_gui;
|
let mut p1 = pos_in_gui;
|
||||||
p0[1 - axis as usize] = transform.frame().min[1 - axis as usize];
|
p0[1 - AXIS] = transform.frame().min[1 - AXIS];
|
||||||
p1[1 - axis as usize] = transform.frame().max[1 - axis as usize];
|
p1[1 - AXIS] = transform.frame().max[1 - AXIS];
|
||||||
|
|
||||||
if let Some(clamp_range) = clamp_range {
|
if let Some(clamp_range) = clamp_range {
|
||||||
if axis == Axis::X {
|
if AXIS == X_AXIS {
|
||||||
p0.y = transform.position_from_point_y(clamp_range.min[1]);
|
p0.y = transform.position_from_point_y(clamp_range.min[1]);
|
||||||
p1.y = transform.position_from_point_y(clamp_range.max[1]);
|
p1.y = transform.position_from_point_y(clamp_range.max[1]);
|
||||||
} else {
|
} else {
|
||||||
@@ -1633,7 +1722,7 @@ impl PreparedPlot {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if sharp_grid_lines {
|
if self.sharp_grid_lines {
|
||||||
// Round to avoid aliasing
|
// Round to avoid aliasing
|
||||||
p0 = ui.ctx().round_pos_to_pixels(p0);
|
p0 = ui.ctx().round_pos_to_pixels(p0);
|
||||||
p1 = ui.ctx().round_pos_to_pixels(p1);
|
p1 = ui.ctx().round_pos_to_pixels(p1);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::f64::consts::TAU;
|
use std::f64::consts::TAU;
|
||||||
use std::ops::RangeInclusive;
|
use std::ops::RangeInclusive;
|
||||||
|
|
||||||
use egui::plot::{AxisBools, AxisConfig, GridInput, GridMark, PlotResponse};
|
use egui::plot::{AxisBools, GridInput, GridMark, PlotResponse};
|
||||||
use egui::*;
|
use egui::*;
|
||||||
use plot::{
|
use plot::{
|
||||||
Arrows, Bar, BarChart, BoxElem, BoxPlot, BoxSpread, CoordinatesFormatter, Corner, HLine,
|
Arrows, Bar, BarChart, BoxElem, BoxPlot, BoxSpread, CoordinatesFormatter, Corner, HLine,
|
||||||
@@ -265,10 +265,8 @@ impl LineDemo {
|
|||||||
self.time += ui.input(|i| i.unstable_dt).at_most(1.0 / 30.0) as f64;
|
self.time += ui.input(|i| i.unstable_dt).at_most(1.0 / 30.0) as f64;
|
||||||
};
|
};
|
||||||
let mut plot = Plot::new("lines_demo")
|
let mut plot = Plot::new("lines_demo")
|
||||||
.axes(vec![
|
.x_axis_label("x".to_string())
|
||||||
AxisConfig::default(plot::Axis::X).label("x".to_string()),
|
.y_axis_label("y".to_string())
|
||||||
AxisConfig::default(plot::Axis::Y).label("y".to_string()),
|
|
||||||
])
|
|
||||||
.legend(Legend::default());
|
.legend(Legend::default());
|
||||||
if self.square {
|
if self.square {
|
||||||
plot = plot.view_aspect(1.0);
|
plot = plot.view_aspect(1.0);
|
||||||
@@ -554,19 +552,12 @@ impl CustomAxisDemo {
|
|||||||
|
|
||||||
ui.label("Zoom in on the X-axis to see hours and minutes");
|
ui.label("Zoom in on the X-axis to see hours and minutes");
|
||||||
|
|
||||||
let axes = vec![
|
|
||||||
AxisConfig::default(plot::Axis::X)
|
|
||||||
.tick_formatter(x_fmt)
|
|
||||||
.label("Percent".to_string()),
|
|
||||||
AxisConfig::default(plot::Axis::Y)
|
|
||||||
.tick_formatter(y_fmt)
|
|
||||||
.max_digits(4)
|
|
||||||
.label("Time".to_string()),
|
|
||||||
];
|
|
||||||
|
|
||||||
Plot::new("custom_axes")
|
Plot::new("custom_axes")
|
||||||
.data_aspect(2.0 * MINS_PER_DAY as f32)
|
.data_aspect(2.0 * MINS_PER_DAY as f32)
|
||||||
.axes(axes)
|
.x_axis_label("Percent".to_string())
|
||||||
|
.x_axis_formatter(x_fmt)
|
||||||
|
.y_axis_label("Time".to_string())
|
||||||
|
.y_axis_formatter(y_fmt)
|
||||||
.x_grid_spacer(CustomAxisDemo::x_grid)
|
.x_grid_spacer(CustomAxisDemo::x_grid)
|
||||||
.label_formatter(label_fmt)
|
.label_formatter(label_fmt)
|
||||||
.show(ui, |plot_ui| {
|
.show(ui, |plot_ui| {
|
||||||
|
|||||||
Reference in New Issue
Block a user