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}