Skip to main content

egui_charts/model/
chart_type.rs

1//! Chart Type Definitions
2//!
3//! Defines all chart visualization types supported by the charting library.
4//! This module provides the canonical definition of chart types, their metadata,
5//! categories, and configuration requirements.
6//!
7//! # Terminology
8//!
9//! | Context        | Term to use         | Notes                                      |
10//! |----------------|---------------------|--------------------------------------------|
11//! | Enum variant   | `Candles`           | Short form, matches Rust naming convention  |
12//! | Doc comments   | "candlestick"       | Full technical name (Japanese candlesticks) |
13//! | Heikin-Ashi    | Always hyphenated   | Enum variant stays `Heikin`                 |
14//! | SeriesType     | `Candles`           | Matches `ChartType::Candles`                |
15//!
16//! # Chart Types
17//! - Standard: Bars, Candles, Hollow Candles, Volume Candles
18//! - Line-based: Line, Line with Markers, Step Line
19//! - Area-based: Area, HLC Area, Baseline
20//! - Japanese: Renko, Kagi, Line Break, Heikin-Ashi
21//! - Range-based: Range Bars, Point & Figure
22//! - Advanced: Volume Footprint, TPO, Session Volume, High-Low
23
24use std::fmt;
25
26/// Chart visualization types
27///
28/// Each variant represents a distinct way to visualize OHLCV data.
29/// The default is `Candles` (Japanese candlesticks).
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
31pub enum ChartType {
32    // === Standard OHLC Types ===
33    /// Traditional OHLC bars with tick marks
34    Bars,
35    /// Japanese candlesticks (default)
36    #[default]
37    Candles,
38    /// Hollow when close > open, filled when close < open
39    HollowCandles,
40    /// Candle width proportional to volume
41    VolumeCandles,
42
43    // === Line-Based Types ===
44    /// Simple close price line
45    Line,
46    /// Line with circular markers at each data point
47    LineWithMarkers,
48    /// Stepped line (horizontal then vertical)
49    StepLine,
50
51    // === Area-Based Types ===
52    /// Filled area under close price line
53    Area,
54    /// High-Low-Close area chart
55    HlcArea,
56    /// Baseline comparison chart with colored fill
57    Baseline,
58
59    // === Range Types ===
60    /// High-Low range visualization
61    HighLow,
62    /// Range bars (price-based, time-independent)
63    Range,
64
65    // === Japanese Chart Types ===
66    /// Renko bricks (price movement only)
67    Renko,
68    /// Kagi chart (trend reversals)
69    Kagi,
70    /// Three line break chart
71    LineBreak,
72    /// Heikin-Ashi candles (smoothed)
73    Heikin,
74    /// Point and Figure (X and O columns)
75    PointAndFigure,
76
77    // === Advanced/Professional Types ===
78    /// Volume footprint / order flow
79    VolumeFootprint,
80    /// Time Price Opportunity (Market Profile)
81    TimePriceOpportunity,
82    /// Volume aggregated by session
83    SessionVolume,
84}
85
86/// Categories for grouping chart types in the UI
87#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
88pub enum ChartTypeCategory {
89    /// Standard OHLC visualizations
90    Standard,
91    /// Line-based charts
92    LineBased,
93    /// Area/filled charts
94    AreaBased,
95    /// Japanese charting techniques
96    Japanese,
97    /// Range/price-based charts
98    RangeBased,
99    /// Advanced professional charts
100    Advanced,
101}
102
103impl ChartTypeCategory {
104    /// Display name for the category
105    pub fn name(&self) -> &'static str {
106        match self {
107            Self::Standard => "Standard",
108            Self::LineBased => "Line",
109            Self::AreaBased => "Area",
110            Self::Japanese => "Japanese",
111            Self::RangeBased => "Range",
112            Self::Advanced => "Advanced",
113        }
114    }
115
116    /// All categories in display order
117    pub fn all() -> &'static [ChartTypeCategory] {
118        &[
119            Self::Standard,
120            Self::LineBased,
121            Self::AreaBased,
122            Self::Japanese,
123            Self::RangeBased,
124            Self::Advanced,
125        ]
126    }
127}
128
129impl ChartType {
130    /// All chart types in display order
131    pub fn all() -> &'static [ChartType] {
132        &[
133            ChartType::Bars,
134            ChartType::Candles,
135            ChartType::HollowCandles,
136            ChartType::VolumeCandles,
137            ChartType::Line,
138            ChartType::LineWithMarkers,
139            ChartType::StepLine,
140            ChartType::Area,
141            ChartType::HlcArea,
142            ChartType::Baseline,
143            ChartType::HighLow,
144            ChartType::VolumeFootprint,
145            ChartType::TimePriceOpportunity,
146            ChartType::SessionVolume,
147            ChartType::LineBreak,
148            ChartType::Kagi,
149            ChartType::Range,
150            ChartType::PointAndFigure,
151            ChartType::Renko,
152            ChartType::Heikin,
153        ]
154    }
155
156    /// User-facing display name
157    pub fn name(&self) -> &'static str {
158        match self {
159            ChartType::Bars => "Bars",
160            ChartType::Candles => "Candles",
161            ChartType::HollowCandles => "Hollow candles",
162            ChartType::VolumeCandles => "Volume candles",
163            ChartType::Line => "Line",
164            ChartType::LineWithMarkers => "Line with markers",
165            ChartType::StepLine => "Step line",
166            ChartType::Area => "Area",
167            ChartType::HlcArea => "HLC area",
168            ChartType::Baseline => "Baseline",
169            ChartType::HighLow => "High-low",
170            ChartType::VolumeFootprint => "Volume footprint",
171            ChartType::TimePriceOpportunity => "Time Price Opportunity",
172            ChartType::SessionVolume => "Session volume",
173            ChartType::LineBreak => "Line break",
174            ChartType::Kagi => "Kagi",
175            ChartType::Range => "Range",
176            ChartType::PointAndFigure => "Point & Figure",
177            ChartType::Renko => "Renko",
178            ChartType::Heikin => "Heikin Ashi",
179        }
180    }
181
182    /// Alias for name() for string compatibility
183    pub fn as_str(&self) -> &'static str {
184        self.name()
185    }
186
187    /// Technical description for tooltips
188    pub fn description(&self) -> &'static str {
189        match self {
190            ChartType::Bars => "OHLC bars with tick marks",
191            ChartType::Candles => "Japanese candlesticks",
192            ChartType::HollowCandles => "Hollow when close > open",
193            ChartType::VolumeCandles => "Width based on volume",
194            ChartType::Line => "Close price line",
195            ChartType::LineWithMarkers => "Line with data points",
196            ChartType::StepLine => "Stepped line chart",
197            ChartType::Area => "Filled area chart",
198            ChartType::HlcArea => "High-Low-Close area",
199            ChartType::Baseline => "Baseline comparison",
200            ChartType::HighLow => "High-Low range",
201            ChartType::VolumeFootprint => "Order flow analysis",
202            ChartType::TimePriceOpportunity => "TPO / Market Profile",
203            ChartType::SessionVolume => "Volume by session",
204            ChartType::LineBreak => "Three line break",
205            ChartType::Kagi => "Kagi chart",
206            ChartType::Range => "Range bars",
207            ChartType::PointAndFigure => "X and O chart",
208            ChartType::Renko => "Renko bricks",
209            ChartType::Heikin => "Heikin-Ashi candles",
210        }
211    }
212
213    /// Alias for description() for compatibility
214    pub fn desc(&self) -> &'static str {
215        self.description()
216    }
217
218    /// Category this chart type belongs to
219    pub fn category(&self) -> ChartTypeCategory {
220        match self {
221            ChartType::Bars
222            | ChartType::Candles
223            | ChartType::HollowCandles
224            | ChartType::VolumeCandles => ChartTypeCategory::Standard,
225
226            ChartType::Line | ChartType::LineWithMarkers | ChartType::StepLine => {
227                ChartTypeCategory::LineBased
228            }
229
230            ChartType::Area | ChartType::HlcArea | ChartType::Baseline => {
231                ChartTypeCategory::AreaBased
232            }
233
234            ChartType::Renko
235            | ChartType::Kagi
236            | ChartType::LineBreak
237            | ChartType::Heikin
238            | ChartType::PointAndFigure => ChartTypeCategory::Japanese,
239
240            ChartType::HighLow | ChartType::Range => ChartTypeCategory::RangeBased,
241
242            ChartType::VolumeFootprint
243            | ChartType::TimePriceOpportunity
244            | ChartType::SessionVolume => ChartTypeCategory::Advanced,
245        }
246    }
247
248    /// Get all chart types in a specific category
249    pub fn in_category(category: ChartTypeCategory) -> Vec<ChartType> {
250        Self::all()
251            .iter()
252            .copied()
253            .filter(|ct| ct.category() == category)
254            .collect()
255    }
256
257    /// Whether this chart type uses OHLC data (vs single value)
258    pub fn uses_ohlc(&self) -> bool {
259        match self {
260            ChartType::Bars
261            | ChartType::Candles
262            | ChartType::HollowCandles
263            | ChartType::VolumeCandles
264            | ChartType::HlcArea
265            | ChartType::HighLow
266            | ChartType::Renko
267            | ChartType::Kagi
268            | ChartType::LineBreak
269            | ChartType::Heikin
270            | ChartType::Range
271            | ChartType::PointAndFigure
272            | ChartType::VolumeFootprint
273            | ChartType::TimePriceOpportunity
274            | ChartType::SessionVolume => true,
275
276            ChartType::Line
277            | ChartType::LineWithMarkers
278            | ChartType::StepLine
279            | ChartType::Area
280            | ChartType::Baseline => false,
281        }
282    }
283
284    /// Whether this chart type supports volume display
285    pub fn supports_volume(&self) -> bool {
286        match self {
287            // Time-independent charts don't align with volume
288            ChartType::Renko
289            | ChartType::Kagi
290            | ChartType::PointAndFigure
291            | ChartType::Range
292            | ChartType::LineBreak => false,
293
294            // Volume footprint IS the volume display
295            ChartType::VolumeFootprint | ChartType::SessionVolume => false,
296
297            _ => true,
298        }
299    }
300
301    /// Whether this chart type requires special parameters
302    pub fn requires_parameters(&self) -> bool {
303        matches!(
304            self,
305            ChartType::Renko
306                | ChartType::Kagi
307                | ChartType::Range
308                | ChartType::PointAndFigure
309                | ChartType::LineBreak
310                | ChartType::Baseline
311        )
312    }
313
314    /// Whether this chart type is time-independent
315    pub fn is_time_independent(&self) -> bool {
316        matches!(
317            self,
318            ChartType::Renko
319                | ChartType::Kagi
320                | ChartType::Range
321                | ChartType::PointAndFigure
322                | ChartType::LineBreak
323        )
324    }
325
326    /// Whether this chart type transforms the input data
327    pub fn transforms_data(&self) -> bool {
328        matches!(
329            self,
330            ChartType::Renko
331                | ChartType::Kagi
332                | ChartType::Range
333                | ChartType::PointAndFigure
334                | ChartType::LineBreak
335                | ChartType::Heikin
336        )
337    }
338}
339
340impl fmt::Display for ChartType {
341    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
342        write!(f, "{}", self.name())
343    }
344}
345
346// === ChartStyle <-> ChartType Conversions ===
347
348use crate::model::enums::ChartStyle;
349
350/// Every TV `ChartStyle` maps to a domain `ChartType`.
351impl From<ChartStyle> for ChartType {
352    fn from(style: ChartStyle) -> Self {
353        match style {
354            ChartStyle::Bar => ChartType::Bars,
355            ChartStyle::Candle => ChartType::Candles,
356            ChartStyle::Line => ChartType::Line,
357            ChartStyle::Area => ChartType::Area,
358            ChartStyle::Renko => ChartType::Renko,
359            ChartStyle::Kagi => ChartType::Kagi,
360            ChartStyle::PnF => ChartType::PointAndFigure,
361            ChartStyle::LineBreak => ChartType::LineBreak,
362            ChartStyle::HeikinAshi => ChartType::Heikin,
363            ChartStyle::HollowCandle => ChartType::HollowCandles,
364            ChartStyle::Baseline => ChartType::Baseline,
365            ChartStyle::HighLow => ChartType::HighLow,
366            ChartStyle::Column => ChartType::Bars, // closest match
367            ChartStyle::LineWithMarkers => ChartType::LineWithMarkers,
368            ChartStyle::Stepline => ChartType::StepLine,
369            ChartStyle::HLCArea => ChartType::HlcArea,
370            ChartStyle::VolCandle => ChartType::VolumeCandles,
371            ChartStyle::HLCBars => ChartType::HighLow, // closest match
372        }
373    }
374}
375
376impl ChartType {
377    /// Convert to `ChartStyle`, if a mapping exists.
378    ///
379    /// Domain-only types (VolumeFootprint, TimePriceOpportunity, SessionVolume, Range)
380    /// have no wire-format equivalent and return `None`.
381    pub fn to_chart_style(&self) -> Option<ChartStyle> {
382        match self {
383            ChartType::Bars => Some(ChartStyle::Bar),
384            ChartType::Candles => Some(ChartStyle::Candle),
385            ChartType::HollowCandles => Some(ChartStyle::HollowCandle),
386            ChartType::VolumeCandles => Some(ChartStyle::VolCandle),
387            ChartType::Line => Some(ChartStyle::Line),
388            ChartType::LineWithMarkers => Some(ChartStyle::LineWithMarkers),
389            ChartType::StepLine => Some(ChartStyle::Stepline),
390            ChartType::Area => Some(ChartStyle::Area),
391            ChartType::HlcArea => Some(ChartStyle::HLCArea),
392            ChartType::Baseline => Some(ChartStyle::Baseline),
393            ChartType::HighLow => Some(ChartStyle::HighLow),
394            ChartType::Renko => Some(ChartStyle::Renko),
395            ChartType::Kagi => Some(ChartStyle::Kagi),
396            ChartType::LineBreak => Some(ChartStyle::LineBreak),
397            ChartType::Heikin => Some(ChartStyle::HeikinAshi),
398            ChartType::PointAndFigure => Some(ChartStyle::PnF),
399            // Domain-only types with no TV equivalent
400            ChartType::Range
401            | ChartType::VolumeFootprint
402            | ChartType::TimePriceOpportunity
403            | ChartType::SessionVolume => None,
404        }
405    }
406}
407
408/// Configuration parameters for chart types that require them
409#[derive(Debug, Clone)]
410pub struct ChartTypeParams {
411    /// Renko brick size (price units)
412    pub renko_brick_size: f64,
413    /// Kagi reversal amount (price units or percentage)
414    pub kagi_reversal: f64,
415    /// Range bar size (price units)
416    pub range_size: f64,
417    /// Point & Figure box size
418    pub pnf_box_size: f64,
419    /// Point & Figure reversal count
420    pub pnf_reversal: u32,
421    /// Line break line count (typically 3)
422    pub line_break_count: usize,
423    /// Baseline price level
424    pub baseline_price: f64,
425}
426
427impl Default for ChartTypeParams {
428    fn default() -> Self {
429        Self {
430            renko_brick_size: 1.0,
431            kagi_reversal: 4.0,
432            range_size: 10.0,
433            pnf_box_size: 1.0,
434            pnf_reversal: 3,
435            line_break_count: 3,
436            baseline_price: 0.0, // Will be calculated from data
437        }
438    }
439}
440
441impl ChartTypeParams {
442    /// Create with ATR-based Renko brick size
443    pub fn with_atr_renko(atr: f64, multiplier: f64) -> Self {
444        Self {
445            renko_brick_size: atr * multiplier,
446            ..Default::default()
447        }
448    }
449}
450
451#[cfg(test)]
452mod tests {
453    use super::*;
454
455    #[test]
456    fn test_all_chart_types_count() {
457        assert_eq!(ChartType::all().len(), 20);
458    }
459
460    #[test]
461    fn test_default_is_candles() {
462        assert_eq!(ChartType::default(), ChartType::Candles);
463    }
464
465    #[test]
466    fn test_category_grouping() {
467        let standard = ChartType::in_category(ChartTypeCategory::Standard);
468        assert!(standard.contains(&ChartType::Candles));
469        assert!(standard.contains(&ChartType::Bars));
470        assert!(!standard.contains(&ChartType::Line));
471    }
472
473    #[test]
474    fn test_ohlc_vs_single_value() {
475        assert!(ChartType::Candles.uses_ohlc());
476        assert!(!ChartType::Line.uses_ohlc());
477    }
478
479    #[test]
480    fn test_volume_support() {
481        assert!(ChartType::Candles.supports_volume());
482        assert!(!ChartType::Renko.supports_volume());
483    }
484
485    #[test]
486    fn test_time_independence() {
487        assert!(ChartType::Renko.is_time_independent());
488        assert!(!ChartType::Candles.is_time_independent());
489    }
490
491    #[test]
492    fn test_display_trait() {
493        assert_eq!(format!("{}", ChartType::Candles), "Candles");
494        assert_eq!(format!("{}", ChartType::Heikin), "Heikin Ashi");
495    }
496}