1use eframe::{egui, NativeOptions};
18use liveplot::{channel_plot, LivePlotPanel, PlotPoint, PlotSink, Trace, TracesController};
19use std::time::Duration;
20
21const COLS: usize = 20;
22const ROWS: usize = 15;
23const TOTAL: usize = COLS * ROWS;
24
25const UPDATE_MS: u64 = 16;
27const DEFAULT_SAMPLES_PER_SEC: f64 = 60.0;
30const DEFAULT_SINE_HZ: f64 = 1.5;
32
33fn hsv_to_rgb(h: f64, s: f64, v: f64) -> [u8; 3] {
34 let h6 = (h.fract() * 6.0).max(0.0);
36 let i = h6.floor() as i32;
37 let f = h6 - (i as f64);
38 let p = v * (1.0 - s);
39 let q = v * (1.0 - f * s);
40 let t = v * (1.0 - (1.0 - f) * s);
41 let (r, g, b) = match i.rem_euclid(6) {
42 0 => (v, t, p),
43 1 => (q, v, p),
44 2 => (p, v, t),
45 3 => (p, q, v),
46 4 => (t, p, v),
47 5 => (v, p, q),
48 _ => (v, p, q),
49 };
50 [
51 (r.clamp(0.0, 1.0) * 255.0) as u8,
52 (g.clamp(0.0, 1.0) * 255.0) as u8,
53 (b.clamp(0.0, 1.0) * 255.0) as u8,
54 ]
55}
56
57struct TinyPlot {
58 sink: PlotSink,
59 trace: Trace,
60 phase_cycles: f64,
61}
62
63impl TinyPlot {
64 fn new(label: &str, phase_cycles: f64, color_hint: [u8; 3]) -> (Self, LivePlotPanel) {
65 let (sink, rx) = channel_plot();
66 let trace = sink.create_trace(label, None);
67
68 let mut panel = LivePlotPanel::new(rx);
69 panel.traces_data.max_points = 2_000;
71 panel.compact = true;
73 panel.top_bar_buttons = Some(vec![]);
75 panel.sidebar_buttons = Some(vec![]);
76 panel.min_height_for_top_bar = 0.0;
77 panel.min_width_for_sidebar = 0.0;
78 panel.min_height_for_sidebar = 0.0;
79 for s in panel.liveplot_panel.get_data_mut() {
80 s.time_window = 4.0;
81 s.force_hide_legend = true;
83 }
84
85 let ctrl = TracesController::new();
87 panel.set_controllers(None, None, Some(ctrl.clone()), None, None, None, None);
88 ctrl.request_set_color(label, color_hint);
89
90 (
91 Self {
92 sink,
93 trace,
94 phase_cycles,
95 },
96 panel,
97 )
98 }
99
100 fn feed(&self, t: f64, freq_hz: f64) {
101 const TAU: f64 = std::f64::consts::TAU;
102 let y = ((t * freq_hz + self.phase_cycles) * TAU).sin();
103 let _ = self.sink.send_point(&self.trace, PlotPoint { x: t, y });
104 }
105}
106
107struct LotsOfTinyPlotsApp {
108 plots: Vec<(TinyPlot, LivePlotPanel)>,
109 last_window_size: egui::Vec2,
111 samples_per_second: f64,
113 sine_hz: f64,
115 last_sample_time: f64,
118}
119
120impl LotsOfTinyPlotsApp {
121 fn new(samples_per_second: f64, sine_hz: f64) -> Self {
122 let now_us = chrono::Utc::now().timestamp_micros();
123 let start_t = (now_us as f64) * 1e-6;
124
125 let mut plots = Vec::with_capacity(TOTAL);
126 for i in 0..TOTAL {
127 let phase = (i as f64) / (TOTAL as f64); let hue = (i as f64) / (TOTAL as f64);
129 let col = hsv_to_rgb(hue, 0.85, 0.9);
130 let label = format!("sine_{:03}", i);
131 let (p, mp) = TinyPlot::new(&label, phase, col);
132 plots.push((p, mp));
133 }
134 Self {
135 plots,
136 last_window_size: egui::Vec2::ZERO,
137 samples_per_second,
138 sine_hz,
139 last_sample_time: start_t,
140 }
141 }
142
143 fn render_grid(&mut self, ui: &mut egui::Ui) {
144 let avail = ui.available_size();
146 let (grid_rect, _) = ui.allocate_exact_size(avail, egui::Sense::hover());
147
148 let cell_w = (grid_rect.width() / COLS as f32).floor().max(1.0);
150 let cell_h = (grid_rect.height() / ROWS as f32).floor().max(1.0);
151
152 for row in 0..ROWS {
153 for col in 0..COLS {
154 let idx = row * COLS + col;
155 let x = (grid_rect.left() + col as f32 * cell_w).round();
156 let y = (grid_rect.top() + row as f32 * cell_h).round();
157 let cell_rect =
158 egui::Rect::from_min_size(egui::pos2(x, y), egui::vec2(cell_w, cell_h));
159 let (_p, panel) = &mut self.plots[idx];
160 let mut child_ui =
161 ui.new_child(egui::UiBuilder::new().id_salt(idx).max_rect(cell_rect));
162 panel.update_embedded(&mut child_ui);
163 }
164 }
165 }
166}
167
168impl eframe::App for LotsOfTinyPlotsApp {
169 fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
170 let now_us = chrono::Utc::now().timestamp_micros();
172 let t = (now_us as f64) * 1e-6;
173 let sample_interval = 1.0 / self.samples_per_second;
175 let mut next_time = self.last_sample_time + sample_interval;
176 while next_time <= t {
177 for (p, _) in &self.plots {
178 p.feed(next_time, self.sine_hz);
179 }
180 self.last_sample_time = next_time;
182 next_time += sample_interval;
183 }
184
185 let current_size = ctx.input(|i| i.viewport_rect().size());
187 if self.last_window_size != egui::Vec2::ZERO && self.last_window_size != current_size {
188 for (_p, panel) in &mut self.plots {
189 panel.fit_all_bounds();
190 }
191 }
192 self.last_window_size = current_size;
193
194 egui::CentralPanel::default().show(ctx, |ui| {
195 ui.heading("Lots of tiny sine plots — 20 × 15");
196 ui.label(format!(
197 "Each plot shows the same sine wave shifted by phase; every trace has its own color. — samples: {:.1} Hz, sine: {:.3} Hz",
198 self.samples_per_second,
199 self.sine_hz
200 ));
201 ui.add_space(6.0);
202 self.render_grid(ui);
203 });
204
205 ctx.request_repaint_after(Duration::from_millis(UPDATE_MS));
206 }
207}
208
209fn main() -> eframe::Result<()> {
210 let mut samples_per_second = DEFAULT_SAMPLES_PER_SEC;
212 let mut sine_hz = DEFAULT_SINE_HZ;
213 let mut args = std::env::args().skip(1);
214 while let Some(arg) = args.next() {
215 if arg == "-s" || arg == "--samples-per-second" {
216 if let Some(val) = args.next() {
217 match val.parse::<f64>() {
218 Ok(v) => samples_per_second = v,
219 Err(_) => eprintln!("invalid value for {}: {}", arg, val),
220 }
221 }
222 } else if arg == "-h" || arg == "--hz" {
223 if let Some(val) = args.next() {
224 match val.parse::<f64>() {
225 Ok(v) => sine_hz = v,
226 Err(_) => eprintln!("invalid value for {}: {}", arg, val),
227 }
228 }
229 } else if let Some(rest) = arg.strip_prefix("--samples-per-second=") {
230 match rest.parse::<f64>() {
231 Ok(v) => samples_per_second = v,
232 Err(_) => eprintln!("invalid value for --samples-per-second: {}", rest),
233 }
234 } else if let Some(rest) = arg.strip_prefix("--hz=") {
235 match rest.parse::<f64>() {
236 Ok(v) => sine_hz = v,
237 Err(_) => eprintln!("invalid value for --hz: {}", rest),
238 }
239 } else {
240 }
242 }
243
244 let app = LotsOfTinyPlotsApp::new(samples_per_second, sine_hz);
245 eframe::run_native(
246 "Lots of tiny plots",
247 NativeOptions::default(),
248 Box::new(|_cc| Ok(Box::new(app))),
249 )
250}