feat(plotting): stacked view, incremental refresh, and architecture cleanup#520
Open
VaradhaCodes wants to merge 1 commit into
Open
feat(plotting): stacked view, incremental refresh, and architecture cleanup#520VaradhaCodes wants to merge 1 commit into
VaradhaCodes wants to merge 1 commit into
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Context
Third PR on the plotting module.
pythonPlotting.py→ modular architecture, cursor system, digital timing view, color-coded signal selection, export.Unless something breaks or a new feature gets requested, this module should be stable for a while.
New features
Stacked view
Each visible signal gets its own pane stacked vertically with a shared X axis. Available via the new "Stacked" radio button in the display options (alongside Standard and Digital Timing).
What you can do in stacked view:
Incremental refresh for normal view
Before this, every visibility toggle in normal view called
fig.clear()and rebuilt the entire figure from scratch — around 464ms per click with several traces open. Now, if the plot composition (which traces use which styles, analysis type, legend state) hasn't changed, the toggle just callsset_visible()on the existingLine2Dobjects and redoes autoscale/legend. No teardown, no re-plot.A debounce timer coalesces rapid bursts of clicks so a quick select-all → deselect doesn't trigger N rebuilds.
CL solver freeze for stacked view
After a stacked rebuild's first draw, the constrained_layout solver is dropped and the solved pane positions are pinned. This means subsequent zoom/pan/cursor draws skip the solver entirely. About 20ms saved per draw after the initial build.
Blit cursor drag
Cursor drag now uses matplotlib's blit path: on drag start, the static figure background is snapshotted; each mouse-move event restores the snapshot and redraws only the cursor lines, then blits. Previously each mouse-move triggered a full
draw_idle(). Also defers the per-trace interpolated Y readout to mouse release — computing it on every move was O(N_traces × log(samples)) per event.Keyboard shortcuts
Ctrl+A— select all waveforms in the listF— toggle focus modeData extraction: vectorized + second format support
The parser now does two passes:
Pass 1 (structural): classifies lines and buckets raw data-row strings into column groups. No number parsing.
Pass 2 (bulk): each group's rows go through a single
np.loadtxtcall. For large transient files this replaces millions of per-cellfloat()calls in the Python interpreter with one C-level parse.Also handles newer ngspice output that doesn't emit
*markers between column groups. Old parser stopped reading after the first group on these files and silently dropped nodes.Measured on a 200k-row transient file: 771ms → 316ms (2.4x faster). Output is byte-identical to the original parser — verified across 5 real circuits (transient, AC, DC) and 18 synthetic edge cases.
NgspiceWidget: reliability fixes
sim_end_signalalways fires now. Was only emitted inside success/failure branches, so if_update_ui_after_simulationraised (e.g. widget already destroyed), the signal never fired and the plot window never opened. Moved tofinally.Success check was too strict.
_is_simulation_successfulrequirederror_type == UnknownErroron top of exit code 0. Removed — exit code 0 +NormalExitis enough.xterm command. Was passing a multi-word command string as a single xterm
-earg. Fixed to['-hold', '-e', 'sh', '-c', cmd].Architecture: plot_window.py split into mixins
plot_window.pyhad grown to ~2600 lines. Split into:_cursor_mixin.py_func_trace_mixin.py_list_mixin.py_pane_mixin.py_render_mixin.pyconstants.pymath_utils.py_safe_eval,_format_measurement,_format_frequency,_detect_frequency,_trapztrace.pyTracedataclassplot_window.pynow just wires the mixins together and handles top-level layout and event routing.Switched matplotlib backend from
backend_qt5aggtobackend_qtagg(Qt6 native, not the compatibility shim). RemovedMultimeterWidgetClassfromplotting_widgets.py— it wasn't wired up anywhere.Files
src/ngspiceSimulation/plot_window.pysrc/ngspiceSimulation/data_extraction.pysrc/ngspiceSimulation/NgspiceWidget.pysrc/ngspiceSimulation/plotting_widgets.pysrc/ngspiceSimulation/_cursor_mixin.pysrc/ngspiceSimulation/_func_trace_mixin.pysrc/ngspiceSimulation/_list_mixin.pysrc/ngspiceSimulation/_pane_mixin.pysrc/ngspiceSimulation/_render_mixin.pysrc/ngspiceSimulation/constants.pysrc/ngspiceSimulation/math_utils.pysrc/ngspiceSimulation/trace.pyFollow-up to #506.