Skip to main content

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}