1use std::time::Duration;
14
15use eframe::{egui, NativeOptions};
16use liveplot::{channel_plot, LivePlotPanel, PlotPoint, PlotSink, Trace, TracesController};
17
18#[derive(Clone, Copy)]
19enum PanelWave {
20 Sine,
21 Cosine,
22 Sum { secondary_freq_hz: f64 },
23 AmMod { modulation_hz: f64 },
24}
25
26struct PlotPanel {
27 wave: PanelWave,
28 freq_hz: f64,
29 phase_cycles: f64,
30 sink: PlotSink,
31 trace: Trace,
32}
33
34impl PlotPanel {
35 fn new(
36 label: &'static str,
37 wave: PanelWave,
38 freq_hz: f64,
39 phase_cycles: f64,
40 color_rgb: Option<[u8; 3]>,
41 ) -> (Self, LivePlotPanel) {
42 let (sink, rx) = channel_plot();
43 let trace = sink.create_trace(label, None);
44 let mut plot = LivePlotPanel::new(rx);
45 for scope in plot.liveplot_panel.get_data_mut() {
46 scope.time_window = 8.0;
47 }
48 plot.traces_data.max_points = 5_000;
49 plot.liveplot_panel.update_data(&plot.traces_data);
50 let ctrl = TracesController::new();
52 plot.set_controllers(None, None, Some(ctrl.clone()), None, None, None, None);
53
54 if let Some(rgb) = color_rgb {
56 ctrl.request_set_color(label, rgb);
57 }
58
59 let panel = Self {
60 wave,
61 freq_hz,
62 phase_cycles,
63 sink,
64 trace,
65 };
66 (panel, plot)
67 }
68
69 fn feed(&self, t: f64) {
70 const TAU: f64 = std::f64::consts::PI * 2.0;
71 let base_phase = (t * self.freq_hz + self.phase_cycles) * TAU;
72 let value = match self.wave {
73 PanelWave::Sine => base_phase.sin(),
74 PanelWave::Cosine => base_phase.cos(),
75 PanelWave::Sum { secondary_freq_hz } => {
76 base_phase.sin() + (t * secondary_freq_hz * TAU).cos()
77 }
78 PanelWave::AmMod { modulation_hz } => {
79 let envelope = 1.0 + 0.5 * (t * modulation_hz * TAU).sin();
80 base_phase.sin() * envelope
81 }
82 };
83 let _ = self
84 .sink
85 .send_point(&self.trace, PlotPoint { x: t, y: value });
86 }
87}
88
89struct DashboardApp {
90 panels: Vec<(PlotPanel, LivePlotPanel)>,
91}
92
93impl DashboardApp {
94 fn new() -> Self {
95 let configs = [
96 ("Sine 1 Hz", PanelWave::Sine, 1.0, 0.0),
97 ("Cosine 0.5 Hz", PanelWave::Cosine, 0.5, 0.25),
98 (
99 "Sine + Cosine",
100 PanelWave::Sum {
101 secondary_freq_hz: 2.0,
102 },
103 1.5,
104 0.0,
105 ),
106 (
107 "AM Sine",
108 PanelWave::AmMod { modulation_hz: 0.2 },
109 0.75,
110 0.5,
111 ),
112 ];
113 let mut panels = Vec::new();
114 for (i, (label, wave, freq, phase)) in configs.into_iter().enumerate() {
115 let color = match i {
117 0 => Some([102, 204, 255]), 1 => Some([255, 120, 120]), 2 => Some([200, 255, 170]), 3 => Some([255, 210, 120]), _ => None,
122 };
123 let (panel, plot) = PlotPanel::new(label, wave, freq, phase, color);
124 panels.push((panel, plot));
125 }
126
127 if let Some((_panel, plot)) = panels.get_mut(2) {
129 if let Some(scope) = plot.liveplot_panel.get_data_mut().first_mut() {
130 scope.y_axis.bounds = (-2.0, 2.0);
131 scope.y_axis.auto_fit = false;
132 }
133 }
134
135 Self { panels }
136 }
137
138 fn render_dashboard(&mut self, ui: &mut egui::Ui) {
139 let cols = 2;
140 let rows = (self.panels.len() + cols - 1) / cols;
141 let avail = ui.available_size();
142 let cell_w = avail.x / cols as f32;
143 let cell_h = avail.y / rows as f32;
144
145 egui::Grid::new("embedded_dashboard_grid")
146 .num_columns(cols)
147 .spacing([0.0, 0.0])
148 .show(ui, |ui| {
149 for (idx, (_panel, plot)) in self.panels.iter_mut().enumerate() {
150 let (_, rect) = ui.allocate_space(egui::vec2(cell_w, cell_h));
151 let mut child_ui = ui.new_child(egui::UiBuilder::new().max_rect(rect));
152 plot.update_embedded(&mut child_ui);
153
154 if idx % cols == cols - 1 {
155 ui.end_row();
156 }
157 }
158 });
159 }
160}
161
162impl eframe::App for DashboardApp {
163 fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
164 let now_us = chrono::Utc::now().timestamp_micros();
165 let t = (now_us as f64) * 1e-6;
166 for (panel, _) in &self.panels {
167 panel.feed(t);
168 }
169
170 egui::CentralPanel::default().show(ctx, |ui| {
171 ui.heading("Embedded LivePlot dashboard");
172 ui.label("Four independent LivePlot instances embedded in a resizable 2 x 2 grid.");
173 ui.add_space(8.0);
174 self.render_dashboard(ui);
175 });
176
177 ctx.request_repaint_after(Duration::from_millis(16));
178 }
179}
180
181fn main() -> eframe::Result<()> {
182 let app = DashboardApp::new();
183 eframe::run_native(
184 "LivePlot embedded dashboard demo",
185 NativeOptions {
186 viewport: egui::ViewportBuilder::default().with_maximized(true),
187 ..NativeOptions::default()
188 },
189 Box::new(|_cc| Ok(Box::new(app))),
190 )
191}