egui_charts/studies/plots.rs
1//! Bands between study outputs or fixed levels, and shapes stored per shape instead of per bar.
2
3use egui::Color32;
4
5/// One boundary of a study band, in the study's value scale.
6#[derive(Clone, Copy, Debug)]
7pub enum FillBoundary {
8 /// A zero-based output line, including its offset and visibility.
9 Plot(usize),
10 /// A constant level across the pane.
11 Level(f64),
12}
13
14/// A band between two plots or levels.
15#[derive(Clone, Copy, Debug)]
16pub struct StudyFill {
17 /// First boundary.
18 pub first: FillBoundary,
19 /// Second boundary.
20 pub second: FillBoundary,
21 /// Colour when the first value is at least the second.
22 pub color: Color32,
23 /// Optional colour when the first value is below the second.
24 pub inverted_color: Option<Color32>,
25}
26
27/// One OHLCV candle spanning an exclusive range of source bar indices.
28#[derive(Clone, Debug)]
29pub struct StudyCandle {
30 /// Loaded source bars covered by this candle; gaps do not invent positions.
31 pub bars: std::ops::Range<usize>,
32 /// Aggregated period, stamped with its opening calendar label.
33 pub bar: crate::model::Bar,
34 /// Output whose colour, stroke width and visibility apply to this candle.
35 pub plot: usize,
36}
37
38/// A price band between two source bars, stored once instead of as a value on every bar.
39#[derive(Clone, Debug, PartialEq)]
40pub struct StudyZone {
41 /// Source bars the band spans, from the centre of the first to the centre of the last, or
42 /// from edge to edge when its study's zones cover whole bars.
43 pub bars: std::ops::Range<usize>,
44 /// Upper price.
45 pub top: f64,
46 /// Lower price.
47 pub bottom: f64,
48 /// Output whose colour, stroke width and visibility apply to this zone: its edges in that
49 /// colour and its band at 15% opacity.
50 pub plot: usize,
51}
52
53/// Where the text of a [`StudyLabel`] sits relative to its point.
54#[derive(Clone, Copy, Debug, PartialEq, Eq)]
55#[non_exhaustive]
56pub enum LabelPlacement {
57 /// Above the point, starting at it and extending right.
58 AboveRight,
59 /// Above the point, ending at it and extending left.
60 AboveLeft,
61 /// Centred above the point.
62 Above,
63 /// Centred below the point.
64 Below,
65}
66
67/// Text beside a study value, stored once per label instead of per bar.
68///
69/// The text is written when the study calculates, so drawing a frame formats nothing.
70#[derive(Clone, Debug, PartialEq)]
71pub struct StudyLabel {
72 /// Source bar the text is attached to; may lie past the last bar, in future whitespace.
73 pub bar: usize,
74 /// Price the text is attached to, in the study's value scale.
75 pub price: f64,
76 /// The text; a line break starts a new line.
77 pub text: String,
78 /// Where the text sits relative to `bar` and `price`.
79 pub placement: LabelPlacement,
80 /// Output whose colour and visibility apply to the text. The text is drawn opaque.
81 pub plot: usize,
82}
83
84#[cfg(feature = "drawings")]
85pub(crate) fn drawing_prices(
86 study: &dyn super::Indicator,
87 bars: std::ops::Range<usize>,
88) -> impl Iterator<Item = f64> + '_ {
89 study
90 .drawings()
91 .iter()
92 .filter_map(move |drawing| {
93 if !study.plot_style(0).visible || !drawing.visible || bars.is_empty() {
94 return None;
95 }
96 let levels = drawing.fib_levels();
97 let extent = if levels.is_some() {
98 &drawing.chart_points[..2]
99 } else {
100 &drawing.chart_points
101 };
102 let first = extent.iter().map(|p| p.bar_idx).reduce(f32::min)?;
103 let last = extent.iter().map(|p| p.bar_idx).reduce(f32::max)?;
104 let left = drawing.extend_left
105 || levels
106 .as_ref()
107 .is_some_and(|(c, _)| c.extend_left || c.show_background);
108 let right = drawing.extend_right
109 || drawing.tool_type == crate::drawings::DrawingToolType::Pitchfork
110 || levels
111 .as_ref()
112 .is_some_and(|(c, _)| c.extend_right || c.show_background);
113 if (!left && first >= bars.end as f32) || (!right && last < bars.start as f32) {
114 return None;
115 }
116 Some(
117 drawing
118 .chart_points
119 .iter()
120 .map(|p| p.price)
121 .chain(levels.into_iter().flat_map(|(_, prices)| prices)),
122 )
123 })
124 .flatten()
125 .filter(|price| price.is_finite())
126}