1use crate::signal::data::RealData;
2use eframe::egui;
3use egui_plot::{GridInput, GridMark, Legend, Line, Plot, PlotPoint, PlotPoints};
4
5use crate::plot::native_options_any_thread;
6use crate::plot::save::SavePlotState;
7
8#[derive(Clone, Copy, Debug, PartialEq, Default)]
9pub struct RealDataPlotOptions {
10 pub log_x: bool,
11}
12
13fn log_grid_spacer(input: GridInput) -> Vec<GridMark> {
15 let (lo, hi) = (input.bounds.0, input.bounds.1);
16 let mut marks = Vec::new();
17
18 let start_exp = lo.floor() as i32 - 1;
19 let end_exp = hi.ceil() as i32 + 1;
20
21 for exp in start_exp..=end_exp {
22 let decade = 10f64.powi(exp);
23 for mult in 1..10 {
24 let val = decade * mult as f64;
25 let log_pos = val.log10();
26 if log_pos < lo || log_pos > hi {
27 continue;
28 }
29 let step_size = match mult {
30 1 => 1.0,
31 2 | 5 => 0.3,
32 _ => 0.1,
33 };
34 marks.push(GridMark {
35 value: log_pos,
36 step_size,
37 });
38 }
39 }
40 marks
41}
42
43pub(crate) struct RealDataPlot {
44 channels: Vec<Vec<PlotPoint>>,
45 options: RealDataPlotOptions,
46 x_label: String,
47 y_label: String,
48 save: SavePlotState,
49}
50
51impl RealDataPlot {
52 pub(crate) fn new(
53 data: &RealData,
54 title: &str,
55 x_label: impl Into<String>,
56 y_label: impl Into<String>,
57 options: RealDataPlotOptions,
58 ) -> Self {
59 let x_axis = data.x_axis();
60 let channels = data
61 .channel_iter()
62 .map(|ch| {
63 x_axis
64 .iter()
65 .zip(ch.iter())
66 .filter(|&(x, _)| !options.log_x || x > 0.0)
67 .map(|(x, &y)| {
68 let px = if options.log_x { x.log10() } else { x };
69 PlotPoint::new(px, y)
70 })
71 .collect()
72 })
73 .collect();
74 Self {
75 channels,
76 options,
77 x_label: x_label.into(),
78 y_label: y_label.into(),
79 save: SavePlotState::new(title),
80 }
81 }
82}
83
84impl eframe::App for RealDataPlot {
85 fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
86 egui::Panel::top("controls").show(ui, |ui| {
87 self.save.show_panel(ui);
88 });
89 egui::CentralPanel::default().show(ui, |ui| {
90 let mut plot = Plot::new("real_data")
91 .x_axis_label(&self.x_label)
92 .y_axis_label(&self.y_label)
93 .legend(Legend::default());
94
95 if self.options.log_x {
96 plot = plot
97 .x_grid_spacer(log_grid_spacer)
98 .x_axis_formatter(|mark, _range| {
99 let v = 10f64.powf(mark.value);
100 if v >= 1000.0 {
101 format!("{:.3}k", v / 1000.0)
102 } else {
103 format!("{v:.4}")
104 }
105 })
106 .label_formatter(|pos| {
107 let (name, point) = match pos {
108 egui_plot::HoverPosition::NearDataPoint {
109 plot_name,
110 position,
111 ..
112 } => (*plot_name, position),
113 egui_plot::HoverPosition::Elsewhere { position } => ("", position),
114 };
115 let v = 10f64.powf(point.x);
116 let x_str = if v >= 1000.0 {
117 format!("{:.3}k", v / 1000.0)
118 } else {
119 format!("{v:.4}")
120 };
121 Some(if name.is_empty() {
122 format!("{x_str}\n{:.4}", point.y)
123 } else {
124 format!("{name}\n{x_str}\n{:.4}", point.y)
125 })
126 });
127 }
128
129 plot.show(ui, |plot_ui| {
130 for (i, points) in self.channels.iter().enumerate() {
131 plot_ui.line(Line::new(
132 format!("Channel {i}"),
133 PlotPoints::Borrowed(points),
134 ));
135 }
136 });
137 });
138 self.save.handle_screenshot(ui.ctx());
139 }
140}
141
142pub fn show_real_data(
143 title: &str,
144 data: &RealData,
145 x_label: impl Into<String>,
146 y_label: impl Into<String>,
147 options: RealDataPlotOptions,
148) -> Result<(), crate::plot::Error> {
149 Ok(eframe::run_native(
150 title,
151 native_options_any_thread(),
152 Box::new(|_cc| {
153 Ok(Box::new(RealDataPlot::new(
154 data, title, x_label, y_label, options,
155 )))
156 }),
157 )?)
158}
159
160#[cfg(test)]
161mod tests {
162 use crate::signal::data::RealData;
163
164 use super::*;
165
166 #[test]
167 #[ignore]
168 fn test_show_real_data() {
169 use crate::signal::ndarray::{Array1, Axis, stack};
170
171 let x = Array1::linspace(0.0, 1.0, 500);
172 let data = RealData::new(
173 x.clone(),
174 stack![Axis(0), x.mapv(|t| t.sin()), x.mapv(|t| t.cos())],
175 )
176 .unwrap();
177 show_real_data(
178 "Real Data Test",
179 &data,
180 "x",
181 "y",
182 RealDataPlotOptions::default(),
183 )
184 .unwrap();
185 }
186}