Skip to main content

egui_charts/widget/pane/indicator/
config.rs

1use super::indicator_sizing;
2use crate::scales::{PriceMap, PriceRange, PriceTransform};
3use crate::studies::Indicator;
4use crate::tokens::DESIGN_TOKENS;
5use egui::{Color32, Pos2, Rect, Response};
6
7/// Configuration for an indicator panel's visual appearance and behavior.
8///
9/// Controls the panel height, y-axis range, zone lines (overbought/oversold),
10/// legend display, and grid visibility. Pre-built configurations are available
11/// for common indicators via [`IndicatorPaneConfig::rsi`],
12/// [`IndicatorPaneConfig::macd`], and [`IndicatorPaneConfig::stochastic`].
13#[derive(Debug, Clone)]
14pub struct IndicatorPaneConfig {
15    /// Panel height in egui logical points.
16    pub height: f32,
17    /// Fixed y-axis range (None = auto-scale)
18    pub y_range: Option<(f64, f64)>,
19    /// Show horizontal zone lines (e.g., RSI 70/30)
20    pub show_zones: bool,
21    /// Zone levels, colours and captions. A zone in `Color32::TRANSPARENT` draws no line and
22    /// still bounds the fill.
23    pub zones: Vec<(f64, Color32, &'static str)>,
24    /// Fill between the lowest and highest zone in the study's
25    /// [`Indicator::level_fill_color`].
26    pub fill_between_zones: bool,
27    /// Show legend
28    pub show_legend: bool,
29    /// Show the study name in its legend.
30    pub show_name: bool,
31    /// Host-supplied input caption; empty omits it.
32    pub inputs: String,
33    /// Show the study output values in its legend.
34    pub show_values: bool,
35    /// Show grid
36    pub show_grid: bool,
37}
38
39impl Default for IndicatorPaneConfig {
40    fn default() -> Self {
41        Self {
42            height: DESIGN_TOKENS.sizing.panel.bottom_default_height,
43            y_range: None,
44            show_zones: false,
45            zones: Vec::new(),
46            fill_between_zones: false,
47            show_legend: true,
48            show_name: true,
49            inputs: String::new(),
50            show_values: true,
51            show_grid: true,
52        }
53    }
54}
55
56impl IndicatorPaneConfig {
57    /// Builds the initial appearance from a study's pane metadata.
58    pub fn from_study(study: &dyn Indicator) -> Self {
59        let pane = study.pane();
60        Self {
61            height: pane.height.unwrap_or_else(|| Self::default().height),
62            y_range: pane
63                .range
64                .filter(|(min, max)| min.is_finite() && max.is_finite() && min < max),
65            show_zones: !pane.levels.is_empty(),
66            zones: pane
67                .levels
68                .into_iter()
69                .filter(|(level, _, _)| level.is_finite())
70                .collect(),
71            fill_between_zones: pane.fill_between_levels,
72            ..Self::default()
73        }
74    }
75
76    /// Create config for RSI indicator
77    pub fn rsi() -> Self {
78        Self {
79            height: 138.0,
80            y_range: Some((0.0, 100.0)),
81            show_zones: true,
82            zones: vec![
83                (70.0, Color32::from_rgb(120, 123, 134), "Overbought"),
84                (30.0, Color32::from_rgb(120, 123, 134), "Oversold"),
85            ],
86            fill_between_zones: true,
87            show_legend: true,
88            show_name: true,
89            inputs: String::new(),
90            show_values: true,
91            show_grid: true,
92        }
93    }
94
95    /// Create config for MACD indicator
96    pub fn macd() -> Self {
97        Self {
98            height: indicator_sizing::MULTI_LINE_HEIGHT,
99            y_range: None, // Auto-scale
100            show_zones: true,
101            zones: vec![(
102                0.0,
103                Color32::from_rgba_unmultiplied(120, 123, 134, 128),
104                "Zero Line",
105            )],
106            fill_between_zones: false,
107            show_legend: true,
108            show_name: true,
109            inputs: String::new(),
110            show_values: true,
111            show_grid: true,
112        }
113    }
114
115    /// Create config for Stochastic indicator
116    pub fn stochastic() -> Self {
117        Self {
118            height: indicator_sizing::OSCILLATOR_HEIGHT,
119            y_range: Some((0.0, 100.0)),
120            show_zones: true,
121            zones: vec![
122                (80.0, Color32::from_rgb(120, 123, 134), "Overbought"),
123                (20.0, Color32::from_rgb(120, 123, 134), "Oversold"),
124            ],
125            fill_between_zones: false,
126            show_legend: true,
127            show_name: true,
128            inputs: String::new(),
129            show_values: true,
130            show_grid: true,
131        }
132    }
133}
134
135// Re-export ChartMapping from coords module for x-axis alignment
136pub use crate::chart::coords::ChartMapping;
137
138/// Coordinate parameters for aligning indicator panes with the main chart.
139///
140/// Wraps the parameters needed to construct a [`ChartMapping`] so that
141/// indicator data points are drawn at the same x-positions as their
142/// corresponding bars in the main chart. Obtained from the chart's time scale
143/// after rendering.
144#[derive(Clone, Debug)]
145pub struct IndicatorCoordParams {
146    /// Numeric positions shared with the main chart; `None` keeps ordinal spacing.
147    pub numeric_positions: Option<crate::model::NumericPositions>,
148    /// Points per ordinal slot, or per mean numeric key gap.
149    pub bar_spacing: f32,
150    /// Right offset (bars to the right of last data point)
151    pub right_offset: f64,
152    /// Base index (index of last data point)
153    pub base_idx: usize,
154    /// Start index (index of first visible bar)
155    pub start_idx: usize,
156}
157
158impl IndicatorCoordParams {
159    /// Creates coordinate parameters from the main chart's time-scale state.
160    ///
161    /// These values are typically obtained from the chart after rendering via
162    /// [`crate::widget::Chart::get_chart_mapping`].
163    pub fn new(
164        bar_spacing: f32,
165        right_offset: impl Into<f64>,
166        base_idx: usize,
167        start_idx: usize,
168    ) -> Self {
169        Self {
170            numeric_positions: None,
171            bar_spacing,
172            right_offset: right_offset.into(),
173            base_idx,
174            start_idx,
175        }
176    }
177
178    /// Creates a [`ChartMapping`] from these params, the panel's screen rect, and y-range.
179    ///
180    /// The resulting mapping converts bar indices and price/indicator values to
181    /// screen positions in egui logical points within the given rect.
182    pub fn to_mapping(&self, rect: Rect, y_min: f64, y_max: f64) -> ChartMapping {
183        let mut mapping = ChartMapping::new(
184            rect,
185            self.bar_spacing,
186            self.start_idx,
187            self.base_idx,
188            self.right_offset,
189            PriceMap::new(
190                PriceTransform::default(),
191                PriceRange::new(y_min, y_max),
192                rect.min.y,
193                rect.max.y,
194                false,
195            ),
196        );
197        mapping.numeric_positions = self.numeric_positions.clone();
198        mapping
199    }
200}
201
202/// Layout and interaction results from rendering one interactive indicator pane.
203///
204/// Returned by [`super::IndicatorPane::show_aligned_interactive`]. Besides the rects
205/// and y-axis range used for click hit-testing, it carries the [`Response`] for
206/// the legend's close "x" so the host can offer one-click pane removal.
207#[non_exhaustive]
208pub struct PaneInteraction {
209    /// Full panel rect, including the y-axis label gutter.
210    pub panel_rect: Rect,
211    /// Chart drawing area rect, excluding the y-axis label gutter.
212    pub chart_rect: Rect,
213    /// Y-axis minimum value used for this frame.
214    pub y_min: f64,
215    /// Y-axis maximum value used for this frame.
216    pub y_max: f64,
217    /// Interaction response for the whole pane (line selection, hover).
218    pub response: Response,
219    /// Interaction response for the legend's close "x", or `None` when the pane
220    /// shows no legend and therefore exposes no remove affordance.
221    pub close_response: Option<Response>,
222    /// Visibility button response, when the legend is shown.
223    pub visibility_response: Option<Response>,
224    /// Settings button response, when the legend is shown.
225    pub settings_response: Option<Response>,
226    /// Corner of the study menu, when the legend's more-actions button was clicked or its row
227    /// secondary-clicked this frame.
228    pub menu: Option<Pos2>,
229}