pub struct LivePlotPanel {Show 16 fields
pub traces_data: TracesCollection,
pub overlays: Option<Box<dyn for<'a> FnMut(&mut PlotUi<'_>, &ScopeData, &TracesCollection) + 'static>>,
pub hotkeys: Rc<RefCell<Hotkeys>>,
pub liveplot_panel: LiveplotPanel,
pub right_side_panels: Vec<Box<dyn Panel>>,
pub left_side_panels: Vec<Box<dyn Panel>>,
pub bottom_panels: Vec<Box<dyn Panel>>,
pub detached_panels: Vec<Box<dyn Panel>>,
pub empty_panels: Vec<Box<dyn Panel>>,
pub pending_requests: LivePlotRequests,
pub top_bar_buttons: Option<Vec<ScopeButton>>,
pub sidebar_buttons: Option<Vec<ScopeButton>>,
pub min_height_for_top_bar: f32,
pub min_width_for_sidebar: f32,
pub min_height_for_sidebar: f32,
pub compact: bool,
/* private fields */
}Expand description
The central widget that owns trace data, panels, and the live-plot scope(s).
LivePlotPanel is the building block of the LivePlot UI. It can be used:
- Standalone – wrapped inside
LivePlotAppand driven by the eframe event loop. - Embedded – placed inside a parent egui application via
LivePlotPanel::updateorLivePlotPanel::update_embedded.
§Fields
The struct holds:
- All trace and scope data (
traces_data,liveplot_panel). - A set of configurable sub-panels (traces list, math, thresholds, …).
- Optional controllers that allow programmatic interaction from external code (e.g. pause, export, change colours).
- Responsive-layout parameters that control when the top-bar or sidebar collapse.
Fields§
§traces_data: TracesCollectionCollection of all traces (time-series data) received through the command channel.
overlays: Option<Box<dyn for<'a> FnMut(&mut PlotUi<'_>, &ScopeData, &TracesCollection) + 'static>>Optional plot overlay callback, supplied via configuration.
hotkeys: Rc<RefCell<Hotkeys>>Shared hotkey bindings used by all panels and menu buttons.
liveplot_panel: LiveplotPanelThe primary live-plot panel that renders scope(s) with traces.
right_side_panels: Vec<Box<dyn Panel>>Panels docked to the right side of the plot area.
left_side_panels: Vec<Box<dyn Panel>>Panels docked to the left side of the plot area.
bottom_panels: Vec<Box<dyn Panel>>Panels docked to the bottom of the plot area (e.g. FFT).
detached_panels: Vec<Box<dyn Panel>>Panels shown in detached (floating) windows.
empty_panels: Vec<Box<dyn Panel>>Panels that exist but are not rendered in any dock position (e.g. export dialog).
pending_requests: LivePlotRequestsPending requests (save/load state, add/remove scope) accumulated during one frame.
Buttons placed in the top menu bar. None = the full default set.
Buttons placed in the right sidebar icon strip. None = empty (standard behaviour).
min_height_for_top_bar: f32Minimum plot-area height (px) before the top bar is hidden and its buttons move to sidebar.
Minimum plot-area width (px) before the sidebar is hidden and its buttons move to top bar.
Minimum plot-area height (px) before the sidebar is hidden and its buttons move to top bar.
compact: boolWhen true, the inner CentralPanel is rendered with no frame/margin so the plot
fills every pixel of the allocated space. Useful for dense embedded grid layouts.
Implementations§
Source§impl LivePlotPanel
impl LivePlotPanel
Sourcepub fn apply_controllers_embedded(&mut self, ctx: &Context)
pub fn apply_controllers_embedded(&mut self, ctx: &Context)
Apply controller requests and publish state, for embedded usage (no stand-alone window frame).
This method handles all optional controllers:
- WindowController – publishes viewport size/position, applies resize requests.
- UiActionController – pause/resume, screenshot, raw data export.
- TracesController – colour, visibility, offset, width, style changes; publishes the current trace snapshot.
- ScopesController – add/remove/configure scopes.
- LiveplotController – pause all, clear all, save/load state, window commands.
- FFTController – publishes FFT panel info.
- ThresholdController – threshold add/remove and event publishing
(via [
apply_threshold_controller_requests] and [publish_threshold_events]).
Source§impl LivePlotPanel
impl LivePlotPanel
Sourcepub fn hide_hotkeys_panel(&mut self)
pub fn hide_hotkeys_panel(&mut self)
Hide the Hotkeys panel (useful when focus switches away via hotkeys).
Source§impl LivePlotPanel
impl LivePlotPanel
Sourcepub fn update(&mut self, ui: &mut Ui)
pub fn update(&mut self, ui: &mut Ui)
Main per-frame update: ingest data, render menu / side panels, then draw the plot.
Call this from an egui Ui context each frame. In standalone mode it is
called by LivePlotApp::update; in embedded mode the host
application calls it directly (or via update_embedded).
Sourcepub fn update_embedded(&mut self, ui: &mut Ui)
pub fn update_embedded(&mut self, ui: &mut Ui)
Update and render the panel when embedded in a parent app, then apply controllers.
This is the convenience entry point for embedded use: it calls
update followed by
apply_controllers_embedded.
Examples found in repository?
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 }More examples
53 fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
54 // Apply color scheme
55 self.scheme.apply(ctx);
56
57 egui::TopBottomPanel::top("color_scheme_picker_top").show(ctx, |ui| {
58 ui.horizontal(|ui| {
59 ui.label("Color scheme:");
60 ComboBox::from_id_salt("color_scheme_picker")
61 .selected_text(self.scheme.label())
62 .show_ui(ui, |ui| {
63 for scheme in ColorScheme::all() {
64 let label = scheme.label();
65 if ui.selectable_label(self.scheme == *scheme, label).clicked() {
66 self.scheme = scheme.clone();
67 }
68 }
69 });
70 });
71 });
72
73 egui::CentralPanel::default().show(ctx, |ui| {
74 self.plot.main_panel.update_embedded(ui);
75 });
76
77 ctx.request_repaint_after(std::time::Duration::from_millis(16));
78 }143 fn render_grid(&mut self, ui: &mut egui::Ui) {
144 // Claim the entire remaining area so the grid fills and resizes with the window.
145 let avail = ui.available_size();
146 let (grid_rect, _) = ui.allocate_exact_size(avail, egui::Sense::hover());
147
148 // Floor to whole pixels; use the grid_rect origin for pixel-aligned placement.
149 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 }279 fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
280 // data is produced on a background thread; nothing to do here
281
282 // draw checkboxes at the top
283 egui::TopBottomPanel::top("features_top").show(ctx, |ui| {
284 ui.label("Toggle features:");
285 ui.horizontal_wrapped(|ui| {
286 ui.checkbox(&mut self.features.top_bar, "top_bar");
287 ui.checkbox(&mut self.features.sidebar, "sidebar");
288 ui.checkbox(&mut self.features.markers, "markers");
289 ui.checkbox(&mut self.features.thresholds, "thresholds");
290 ui.checkbox(&mut self.features.triggers, "triggers");
291 ui.checkbox(&mut self.features.measurement, "measurement");
292 ui.checkbox(&mut self.features.export, "export");
293 ui.checkbox(&mut self.features.math, "math");
294 ui.checkbox(&mut self.features.hotkeys, "hotkeys");
295 ui.checkbox(&mut self.features.fft, "fft");
296 ui.checkbox(&mut self.features.x_tick_labels, "x_tick_labels");
297 ui.checkbox(&mut self.features.y_tick_labels, "y_tick_labels");
298 ui.checkbox(&mut self.features.grid, "grid");
299 ui.checkbox(&mut self.features.legend, "legend");
300 ui.checkbox(&mut self.features.scopes, "scopes");
301 ui.checkbox(&mut self.features.pause_resume, "pause_resume");
302 ui.checkbox(&mut self.features.clear_all, "clear_all");
303 });
304 });
305
306 // Apply flags every frame (cost negligible)
307 self.apply_features();
308
309 // render the plot panel
310 egui::CentralPanel::default().show(ctx, |ui| {
311 self.plot.main_panel.update_embedded(ui);
312 });
313
314 // keep redrawing at roughly 60Hz
315 ctx.request_repaint_after(Duration::from_millis(16));
316 }59 fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
60 egui::CentralPanel::default().show(ctx, |ui| {
61 ui.heading("Embedding LivePlot in egui::Window");
62 ui.horizontal(|ui| {
63 ui.label("Select wave:");
64 egui::ComboBox::from_id_salt("wave_select")
65 .selected_text(match self.kind {
66 WaveKind::Sine => "Sine",
67 WaveKind::Cosine => "Cosine",
68 })
69 .show_ui(ui, |ui| {
70 ui.selectable_value(&mut self.kind, WaveKind::Sine, "Sine");
71 ui.selectable_value(&mut self.kind, WaveKind::Cosine, "Cosine");
72 });
73 if ui.button("Open Plot Window").clicked() {
74 self.show_plot_window = true;
75 }
76 });
77 ui.separator();
78 ui.label("Pick a wave and click the button to open the embedded LivePlot window.");
79 });
80
81 // Show the embedded plot in its own egui::Window when requested
82 if self.show_plot_window {
83 let mut open = true;
84 egui::Window::new("LivePlot Window")
85 .open(&mut open)
86 .show(ctx, |ui| {
87 // Optional minimal size for nicer layout
88 ui.set_min_size(egui::vec2(600.0, 300.0));
89 self.plot.main_panel.update_embedded(ui);
90 });
91 if !open {
92 self.show_plot_window = false;
93 }
94 }
95
96 // Feed the chosen wave
97 let now_us = chrono::Utc::now().timestamp_micros();
98 let t = (now_us as f64) * 1e-6;
99 let phase = t * 2.0 * std::f64::consts::PI;
100 let val = match self.kind {
101 WaveKind::Sine => phase.sin(),
102 WaveKind::Cosine => phase.cos(),
103 };
104 let tr = match self.kind {
105 WaveKind::Sine => &self.trace_sine,
106 WaveKind::Cosine => &self.trace_cos,
107 };
108 let _ = self.sink.send_point(tr, PlotPoint { x: t, y: val });
109
110 ctx.request_repaint_after(Duration::from_millis(16));
111 }Sourcepub fn fit_all_bounds(&mut self)
pub fn fit_all_bounds(&mut self)
Programmatically trigger “Fit to View” (both X and Y axes) on every scope.
Call this e.g. after a window resize to ensure all plots fill their bounds.
Examples found in repository?
169 fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
170 // use wall-clock time as sample x
171 let now_us = chrono::Utc::now().timestamp_micros();
172 let t = (now_us as f64) * 1e-6;
173 // generate samples at configured rate; each sample uses the sine frequency
174 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 // advance last_sample_time by one interval per sample generated
181 self.last_sample_time = next_time;
182 next_time += sample_interval;
183 }
184
185 // Detect window resizes and auto-fit all plots when the size changes.
186 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 }Source§impl LivePlotPanel
impl LivePlotPanel
Sourcepub fn new(rx: Receiver<PlotCommand>) -> Self
pub fn new(rx: Receiver<PlotCommand>) -> Self
Create a new LivePlotPanel that will receive PlotCommands from the given channel.
The panel is pre-populated with the default set of sub-panels:
- Right: Traces, Math, Hotkeys, Thresholds, Triggers, Measurement
- Bottom: FFT (when the
fftfeature is enabled) - Hidden: Export
Examples found in repository?
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 // Attach a per-plot traces controller so we can request a custom color
51 let ctrl = TracesController::new();
52 plot.set_controllers(None, None, Some(ctrl.clone()), None, None, None, None);
53
54 // If a color hint is supplied, request it via the traces controller (applied during tick)
55 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 }More examples
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 // keep buffers small for many plots
70 panel.traces_data.max_points = 2_000;
71 // strip all borders/margins so each cell is pure plot
72 panel.compact = true;
73 // suppress top-bar buttons so nothing competes for the tiny space
74 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 // Force-hide the legend overlay to avoid wasting space in tiny cells
82 s.force_hide_legend = true;
83 }
84
85 // Attach a traces controller so we can request a color for this trace
86 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 }Sourcepub fn set_controllers(
&mut self,
window_ctrl: Option<WindowController>,
ui_ctrl: Option<UiActionController>,
traces_ctrl: Option<TracesController>,
scopes_ctrl: Option<ScopesController>,
liveplot_ctrl: Option<LiveplotController>,
fft_ctrl: Option<FFTController>,
threshold_ctrl: Option<ThresholdController>,
)
pub fn set_controllers( &mut self, window_ctrl: Option<WindowController>, ui_ctrl: Option<UiActionController>, traces_ctrl: Option<TracesController>, scopes_ctrl: Option<ScopesController>, liveplot_ctrl: Option<LiveplotController>, fft_ctrl: Option<FFTController>, threshold_ctrl: Option<ThresholdController>, )
Attach controllers for embedded usage.
These mirror the controllers used by LivePlotApp; call this once after
construction to enable programmatic interaction from external code.
Examples found in repository?
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 // Attach a per-plot traces controller so we can request a custom color
51 let ctrl = TracesController::new();
52 plot.set_controllers(None, None, Some(ctrl.clone()), None, None, None, None);
53
54 // If a color hint is supplied, request it via the traces controller (applied during tick)
55 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 }More examples
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 // keep buffers small for many plots
70 panel.traces_data.max_points = 2_000;
71 // strip all borders/margins so each cell is pure plot
72 panel.compact = true;
73 // suppress top-bar buttons so nothing competes for the tiny space
74 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 // Force-hide the legend overlay to avoid wasting space in tiny cells
82 s.force_hide_legend = true;
83 }
84
85 // Attach a traces controller so we can request a color for this trace
86 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 }Sourcepub fn set_event_controller(&mut self, event_ctrl: Option<EventController>)
pub fn set_event_controller(&mut self, event_ctrl: Option<EventController>)
Attach an event controller for event dispatch.
Auto Trait Implementations§
impl Freeze for LivePlotPanel
impl !RefUnwindSafe for LivePlotPanel
impl !Send for LivePlotPanel
impl !Sync for LivePlotPanel
impl Unpin for LivePlotPanel
impl UnsafeUnpin for LivePlotPanel
impl !UnwindSafe for LivePlotPanel
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can
then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.Source§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be
further downcast into Rc<ConcreteType> where ConcreteType implements Trait.Source§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.Source§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>, which can then be
downcast into Box<dyn ConcreteType> where ConcreteType implements Trait.Source§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Rc<Trait> (where Trait: Downcast) to Rc<Any>, which can then be further
downcast into Rc<ConcreteType> where ConcreteType implements Trait.Source§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.Source§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more