egui_charts/model/series_style.rs
1//! How a bar series shows its last value and base line, fits its scale and takes clicks.
2
3use super::Bar;
4use crate::model::LineStyle;
5use egui::Color32;
6use serde::{Deserialize, Serialize};
7use std::sync::Arc;
8
9/// The units a series writes, independently of its price scale's transform.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
11pub enum SeriesFormat {
12 /// Fixed decimal prices.
13 #[default]
14 Price,
15 /// Quantities abbreviated with K, M and B suffixes.
16 Volume,
17 /// Raw percentage units: 12.5 is written as 12.50%, without a base calculation.
18 Percentage,
19}
20
21/// Saved numeric display settings for a bar series. Values and scale modes stay unchanged.
22#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
23#[serde(default)]
24pub struct SeriesDisplayFormat {
25 /// The units and suffix to display.
26 pub kind: SeriesFormat,
27 /// Decimal places, after abbreviation for volume.
28 pub precision: u8,
29 /// Smallest movement in raw units. Labels round to its nearest multiple, with halfway
30 /// values rounded away from zero; raw-price ticks use its multiples. Non-positive or
31 /// non-finite values are ignored, leaving precision to determine rounding and tick spacing.
32 pub min_move: f64,
33}
34
35impl Default for SeriesDisplayFormat {
36 fn default() -> Self {
37 Self {
38 kind: SeriesFormat::Price,
39 precision: 2,
40 min_move: 0.01,
41 }
42 }
43}
44
45/// The bar whose value a series' price line and last value label show.
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
47pub enum PriceLineSource {
48 /// The newest bar, also when it is scrolled out of view.
49 #[default]
50 LastBar,
51 /// The newest bar in view.
52 LastVisible,
53}
54
55/// The range a series contributes when its price scale fits the visible bars.
56///
57/// The function receives the series' visible bars and the range the chart would fit, in prices,
58/// or `None` when they have no finite values. It returns the range to fit instead, as
59/// `(min, max)` in prices, or `None` to leave the series out of the fit. A manual price range
60/// ignores it.
61#[derive(Clone)]
62#[allow(clippy::type_complexity)]
63pub struct AutoscaleProvider(
64 Arc<dyn Fn(&[Bar], Option<(f64, f64)>) -> Option<(f64, f64)> + Send + Sync>,
65);
66
67impl AutoscaleProvider {
68 /// Wraps `provide`, called with the visible bars and the default range on every fit.
69 pub fn new(
70 provide: impl Fn(&[Bar], Option<(f64, f64)>) -> Option<(f64, f64)> + Send + Sync + 'static,
71 ) -> Self {
72 Self(Arc::new(provide))
73 }
74
75 /// The range to fit for `bars`, given the chart's `default` range.
76 pub(crate) fn range(&self, bars: &[Bar], default: Option<(f64, f64)>) -> Option<(f64, f64)> {
77 (self.0)(bars, default)
78 }
79}
80
81impl std::fmt::Debug for AutoscaleProvider {
82 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83 f.write_str("AutoscaleProvider(..)")
84 }
85}
86
87/// Two providers are equal when they share one function.
88impl PartialEq for AutoscaleProvider {
89 fn eq(&self, other: &Self) -> bool {
90 Arc::ptr_eq(&self.0, &other.0)
91 }
92}
93
94/// How the alert levels of one bar series are drawn.
95///
96/// Alerts are not bound to a series, so the host passes the alerts it resolves to a series to
97/// `chart::render_alert_levels` (requires `widget`) with these `labels` and `color`,
98/// and draws none while `visible` is unset.
99#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
100pub struct SeriesAlerts {
101 /// Draw the series' alert levels.
102 pub visible: bool,
103 /// Label each level with its alert.
104 pub labels: bool,
105 /// Colour of the levels and labels.
106 pub color: Color32,
107}
108
109impl Default for SeriesAlerts {
110 fn default() -> Self {
111 Self {
112 visible: true,
113 labels: true,
114 color: Color32::from_rgb(255, 152, 0),
115 }
116 }
117}
118
119/// A bar series' last value label, price line, base line, autoscale, click tolerance and alert
120/// levels.
121///
122/// The chart-wide `ChartConfig::show_symbol_last_val` and `show_price_line` switches hide every
123/// series' labels and price lines.
124#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
125pub struct SeriesStyle {
126 /// Raw-value display for ticks, tags, legends and readouts. `None` retains instrument or
127 /// scale formatting. Explicit series formatters take precedence, followed by the chart's
128 /// price formatter, then this declaration. Percentage and indexed scale modes keep their
129 /// transformed labels. A shared axis takes precision and minimum movement from its first
130 /// visible series; each series keeps its own tag and readout format.
131 #[serde(default, skip_serializing_if = "Option::is_none")]
132 pub display_format: Option<SeriesDisplayFormat>,
133 /// Label the last value on the price axis.
134 pub last_value_visible: bool,
135 /// Draw a horizontal line at the last value.
136 pub price_line_visible: bool,
137 /// The bar whose value the price line and the last value label show.
138 pub price_line_source: PriceLineSource,
139 /// Price line colour; `None` takes the series colour, which for the main series follows the
140 /// direction of the bar.
141 pub price_line_color: Option<Color32>,
142 /// Price line width in logical points.
143 pub price_line_width: f32,
144 /// Price line style.
145 pub price_line_style: LineStyle,
146 /// Draw a line at the series' base value, 0% in percentage mode and 100 in indexed mode.
147 /// Other scale modes have no base value.
148 pub base_line_visible: bool,
149 /// Replaces the range the series contributes to its scale's fit. A function is not saved;
150 /// hosts set it again after restoring a chart.
151 #[serde(skip)]
152 pub autoscale: Option<AutoscaleProvider>,
153 /// Horizontal distance in logical points from a bar's centre within which a click on its
154 /// high to low range selects the main series. Host series do not take clicks.
155 pub hit_tolerance: f32,
156 /// Visibility and style of the series' alert levels.
157 pub alerts: SeriesAlerts,
158}
159
160impl Default for SeriesStyle {
161 fn default() -> Self {
162 Self {
163 display_format: None,
164 last_value_visible: true,
165 price_line_visible: true,
166 price_line_source: PriceLineSource::LastBar,
167 price_line_color: None,
168 price_line_width: 1.0,
169 price_line_style: LineStyle::Dashed,
170 base_line_visible: false,
171 autoscale: None,
172 hit_tolerance: 5.0,
173 alerts: SeriesAlerts::default(),
174 }
175 }
176}