egui_charts/widget/
readout.rs1use super::{Chart, SeriesId};
4use crate::chart::rendering::StudyValue;
5
6impl Chart {
7 pub fn take_indicator_source_request(&mut self) -> Option<crate::studies::StudySource> {
11 self.indicator_source_request.take()
12 }
13
14 pub fn indicator_value_at(&self, id: SeriesId, index: usize) -> Option<(usize, f64)> {
18 let at = self.study_ids.iter().position(|study| *study == id)?;
19 super::menu_value(
20 self.rendered_studies().indicators().get(at)?.as_ref(),
21 index,
22 )
23 }
24
25 pub fn indicator_value_text(&self, id: SeriesId, value: f64) -> Option<String> {
27 let at = self.study_ids.iter().position(|study| *study == id)?;
28 Some(
29 self.rendered_studies()
30 .indicators()
31 .get(at)?
32 .format_value(value),
33 )
34 }
35
36 pub fn study_values_at(&self, index: usize) -> Vec<StudyValue> {
40 self.study_rows_at(index)
41 .into_iter()
42 .map(|(_, row)| row)
43 .collect()
44 }
45
46 pub(super) fn study_rows_at(&self, index: usize) -> Vec<(SeriesId, StudyValue)> {
48 if index >= self.state.shown().len() {
49 return Vec::new();
50 }
51 let mut rows = Vec::new();
52 for (&id, study) in self
53 .study_ids
54 .iter()
55 .zip(self.rendered_studies().indicators())
56 .filter(|(_, study)| study.is_visible())
57 {
58 let names = study.line_names();
59 let colors = study.colors();
60 for line in (0..study.line_count()).filter(|line| study.plot_style(*line).visible) {
61 let row = StudyValue {
62 formatted: study
63 .plot_value(line, index)
64 .map(|value| study.format_plot_value(line, value)),
65 study: study.name().to_owned(),
66 format: study.value_format(),
67 plot: names.get(line).cloned().unwrap_or_default(),
68 value: study.plot_value(line, index),
69 precision: study
70 .precision()
71 .unwrap_or_else(|| match study.value_format() {
72 crate::scales::StudyFormat::Price => self
73 .instrument()
74 .and_then(crate::scales::instrument_price_precision)
75 .map_or(2, usize::from),
76 crate::scales::StudyFormat::Fixed(decimals) => decimals.min(12),
77 crate::scales::StudyFormat::Volume
78 if study
79 .plot_value(line, index)
80 .is_some_and(|value| value.abs() < 1_000.0) =>
81 {
82 0
83 }
84 _ => 2,
85 }),
86 color: colors.get(line).copied().unwrap_or(self.config.text_color),
87 };
88 rows.push((id, row));
89 }
90 }
91 rows
92 }
93}
94
95#[cfg(all(test, feature = "studies"))]
96mod tests {
97 use super::*;
98 use crate::model::{Bar, BarData, ChartType, ChartTypeParams};
99 use crate::widget::test_frames::frame;
100
101 #[test]
102 fn menu_values_follow_displayed_elements() {
103 let bars = [100.0, 130.0, 160.0]
104 .into_iter()
105 .enumerate()
106 .map(|(index, close)| {
107 Bar::from_value(
108 chrono::DateTime::UNIX_EPOCH + chrono::Duration::minutes(index as i64),
109 close,
110 )
111 })
112 .collect();
113 let mut chart = Chart::new(BarData::from_bars(bars));
114 let id = chart.add_indicator(Box::new(crate::studies::SMA::new(3)));
115 chart.set_chart_type(ChartType::Renko);
116 chart.set_chart_type_params(ChartTypeParams {
117 renko_brick_size: 10.0,
118 ..Default::default()
119 });
120 frame(&egui::Context::default(), &mut chart, 0.0, vec![]);
121 assert_eq!(chart.indicator_value_at(id, 2), Some((0, 120.0)));
124 assert_eq!(
125 chart.indicator_value_text(id, 120.0).as_deref(),
126 Some("120.00")
127 );
128 assert_eq!(chart.indicator_value_at(id, 0), None);
129 chart.set_volume_formatter(Some(std::sync::Arc::new(
130 crate::scales::CustomPriceFormatter::new(|value| format!("{value:.0} lots")),
131 )));
132 let volume = chart
133 .indicators()
134 .find(|(_, study)| study.is_volume())
135 .unwrap()
136 .0;
137 let values = chart.study_values_at(2);
138 assert!(
139 values
140 .iter()
141 .any(|row| row.study == "Volume" && row.formatted_value().ends_with(" lots"))
142 );
143 assert!(
144 chart
145 .indicator_value_text(volume, 25.0)
146 .unwrap()
147 .ends_with(" lots")
148 );
149 }
150}