oscura 0.7.0__py3-none-any.whl → 0.10.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- oscura/__init__.py +19 -19
- oscura/analyzers/__init__.py +2 -0
- oscura/analyzers/digital/extraction.py +2 -3
- oscura/analyzers/digital/quality.py +1 -1
- oscura/analyzers/digital/timing.py +1 -1
- oscura/analyzers/eye/__init__.py +5 -1
- oscura/analyzers/eye/generation.py +501 -0
- oscura/analyzers/jitter/__init__.py +6 -6
- oscura/analyzers/jitter/timing.py +419 -0
- oscura/analyzers/patterns/__init__.py +94 -0
- oscura/analyzers/patterns/reverse_engineering.py +991 -0
- oscura/analyzers/power/__init__.py +35 -12
- oscura/analyzers/power/basic.py +3 -3
- oscura/analyzers/power/soa.py +1 -1
- oscura/analyzers/power/switching.py +3 -3
- oscura/analyzers/signal_classification.py +529 -0
- oscura/analyzers/signal_integrity/sparams.py +3 -3
- oscura/analyzers/statistics/__init__.py +4 -0
- oscura/analyzers/statistics/basic.py +152 -0
- oscura/analyzers/statistics/correlation.py +47 -6
- oscura/analyzers/validation.py +1 -1
- oscura/analyzers/waveform/__init__.py +2 -0
- oscura/analyzers/waveform/measurements.py +329 -163
- oscura/analyzers/waveform/measurements_with_uncertainty.py +91 -35
- oscura/analyzers/waveform/spectral.py +498 -54
- oscura/api/dsl/commands.py +15 -6
- oscura/api/server/templates/base.html +137 -146
- oscura/api/server/templates/export.html +84 -110
- oscura/api/server/templates/home.html +248 -267
- oscura/api/server/templates/protocols.html +44 -48
- oscura/api/server/templates/reports.html +27 -35
- oscura/api/server/templates/session_detail.html +68 -78
- oscura/api/server/templates/sessions.html +62 -72
- oscura/api/server/templates/waveforms.html +54 -64
- oscura/automotive/__init__.py +1 -1
- oscura/automotive/can/session.py +1 -1
- oscura/automotive/dbc/generator.py +638 -23
- oscura/automotive/dtc/data.json +102 -17
- oscura/automotive/uds/decoder.py +99 -6
- oscura/cli/analyze.py +8 -2
- oscura/cli/batch.py +36 -5
- oscura/cli/characterize.py +18 -4
- oscura/cli/export.py +47 -5
- oscura/cli/main.py +2 -0
- oscura/cli/onboarding/wizard.py +10 -6
- oscura/cli/pipeline.py +585 -0
- oscura/cli/visualize.py +6 -4
- oscura/convenience.py +400 -32
- oscura/core/config/loader.py +0 -1
- oscura/core/measurement_result.py +286 -0
- oscura/core/progress.py +1 -1
- oscura/core/schemas/device_mapping.json +8 -2
- oscura/core/schemas/packet_format.json +24 -4
- oscura/core/schemas/protocol_definition.json +12 -2
- oscura/core/types.py +300 -199
- oscura/correlation/multi_protocol.py +1 -1
- oscura/export/legacy/__init__.py +11 -0
- oscura/export/legacy/wav.py +75 -0
- oscura/exporters/__init__.py +19 -0
- oscura/exporters/wireshark.py +809 -0
- oscura/hardware/acquisition/file.py +5 -19
- oscura/hardware/acquisition/saleae.py +10 -10
- oscura/hardware/acquisition/socketcan.py +4 -6
- oscura/hardware/acquisition/synthetic.py +1 -5
- oscura/hardware/acquisition/visa.py +6 -6
- oscura/hardware/security/side_channel_detector.py +5 -508
- oscura/inference/message_format.py +686 -1
- oscura/jupyter/display.py +2 -2
- oscura/jupyter/magic.py +3 -3
- oscura/loaders/__init__.py +17 -12
- oscura/loaders/binary.py +1 -1
- oscura/loaders/chipwhisperer.py +1 -2
- oscura/loaders/configurable.py +1 -1
- oscura/loaders/csv_loader.py +2 -2
- oscura/loaders/hdf5_loader.py +1 -1
- oscura/loaders/lazy.py +6 -1
- oscura/loaders/mmap_loader.py +0 -1
- oscura/loaders/numpy_loader.py +8 -7
- oscura/loaders/preprocessing.py +3 -5
- oscura/loaders/rigol.py +21 -7
- oscura/loaders/sigrok.py +2 -5
- oscura/loaders/tdms.py +3 -2
- oscura/loaders/tektronix.py +38 -32
- oscura/loaders/tss.py +20 -27
- oscura/loaders/vcd.py +13 -8
- oscura/loaders/wav.py +1 -6
- oscura/pipeline/__init__.py +76 -0
- oscura/pipeline/handlers/__init__.py +165 -0
- oscura/pipeline/handlers/analyzers.py +1045 -0
- oscura/pipeline/handlers/decoders.py +899 -0
- oscura/pipeline/handlers/exporters.py +1103 -0
- oscura/pipeline/handlers/filters.py +891 -0
- oscura/pipeline/handlers/loaders.py +640 -0
- oscura/pipeline/handlers/transforms.py +768 -0
- oscura/reporting/__init__.py +88 -1
- oscura/reporting/automation.py +348 -0
- oscura/reporting/citations.py +374 -0
- oscura/reporting/core.py +54 -0
- oscura/reporting/formatting/__init__.py +11 -0
- oscura/reporting/formatting/measurements.py +320 -0
- oscura/reporting/html.py +57 -0
- oscura/reporting/interpretation.py +431 -0
- oscura/reporting/summary.py +329 -0
- oscura/reporting/templates/enhanced/protocol_re.html +504 -503
- oscura/reporting/visualization.py +542 -0
- oscura/side_channel/__init__.py +38 -57
- oscura/utils/builders/signal_builder.py +5 -5
- oscura/utils/comparison/compare.py +7 -9
- oscura/utils/comparison/golden.py +1 -1
- oscura/utils/filtering/convenience.py +2 -2
- oscura/utils/math/arithmetic.py +38 -62
- oscura/utils/math/interpolation.py +20 -20
- oscura/utils/pipeline/__init__.py +4 -17
- oscura/utils/progressive.py +1 -4
- oscura/utils/triggering/edge.py +1 -1
- oscura/utils/triggering/pattern.py +2 -2
- oscura/utils/triggering/pulse.py +2 -2
- oscura/utils/triggering/window.py +3 -3
- oscura/validation/hil_testing.py +11 -11
- oscura/visualization/__init__.py +47 -284
- oscura/visualization/batch.py +160 -0
- oscura/visualization/plot.py +542 -53
- oscura/visualization/styles.py +184 -318
- oscura/workflows/__init__.py +2 -0
- oscura/workflows/batch/advanced.py +1 -1
- oscura/workflows/batch/aggregate.py +7 -8
- oscura/workflows/complete_re.py +251 -23
- oscura/workflows/digital.py +27 -4
- oscura/workflows/multi_trace.py +136 -17
- oscura/workflows/waveform.py +788 -0
- {oscura-0.7.0.dist-info → oscura-0.10.0.dist-info}/METADATA +59 -79
- {oscura-0.7.0.dist-info → oscura-0.10.0.dist-info}/RECORD +135 -149
- oscura/side_channel/dpa.py +0 -1025
- oscura/utils/optimization/__init__.py +0 -19
- oscura/utils/optimization/parallel.py +0 -443
- oscura/utils/optimization/search.py +0 -532
- oscura/utils/pipeline/base.py +0 -338
- oscura/utils/pipeline/composition.py +0 -248
- oscura/utils/pipeline/parallel.py +0 -449
- oscura/utils/pipeline/pipeline.py +0 -375
- oscura/utils/search/__init__.py +0 -16
- oscura/utils/search/anomaly.py +0 -424
- oscura/utils/search/context.py +0 -294
- oscura/utils/search/pattern.py +0 -288
- oscura/utils/storage/__init__.py +0 -61
- oscura/utils/storage/database.py +0 -1166
- oscura/visualization/accessibility.py +0 -526
- oscura/visualization/annotations.py +0 -371
- oscura/visualization/axis_scaling.py +0 -305
- oscura/visualization/colors.py +0 -451
- oscura/visualization/digital.py +0 -436
- oscura/visualization/eye.py +0 -571
- oscura/visualization/histogram.py +0 -281
- oscura/visualization/interactive.py +0 -1035
- oscura/visualization/jitter.py +0 -1042
- oscura/visualization/keyboard.py +0 -394
- oscura/visualization/layout.py +0 -400
- oscura/visualization/optimization.py +0 -1079
- oscura/visualization/palettes.py +0 -446
- oscura/visualization/power.py +0 -508
- oscura/visualization/power_extended.py +0 -955
- oscura/visualization/presets.py +0 -469
- oscura/visualization/protocols.py +0 -1246
- oscura/visualization/render.py +0 -223
- oscura/visualization/rendering.py +0 -444
- oscura/visualization/reverse_engineering.py +0 -838
- oscura/visualization/signal_integrity.py +0 -989
- oscura/visualization/specialized.py +0 -643
- oscura/visualization/spectral.py +0 -1226
- oscura/visualization/thumbnails.py +0 -340
- oscura/visualization/time_axis.py +0 -351
- oscura/visualization/waveform.py +0 -454
- {oscura-0.7.0.dist-info → oscura-0.10.0.dist-info}/WHEEL +0 -0
- {oscura-0.7.0.dist-info → oscura-0.10.0.dist-info}/entry_points.txt +0 -0
- {oscura-0.7.0.dist-info → oscura-0.10.0.dist-info}/licenses/LICENSE +0 -0
oscura/visualization/keyboard.py
DELETED
|
@@ -1,394 +0,0 @@
|
|
|
1
|
-
"""Keyboard navigation support for Oscura interactive visualizations.
|
|
2
|
-
|
|
3
|
-
This module provides keyboard navigation handlers for interactive plots
|
|
4
|
-
following WCAG 2.1 accessibility guidelines.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
Example:
|
|
8
|
-
>>> from oscura.visualization.keyboard import KeyboardNavigator
|
|
9
|
-
>>> navigator = KeyboardNavigator(fig, ax)
|
|
10
|
-
>>> navigator.connect()
|
|
11
|
-
|
|
12
|
-
References:
|
|
13
|
-
- WCAG 2.1 Guideline 2.1: Keyboard Accessible
|
|
14
|
-
- WAI-ARIA Authoring Practices for interactive widgets
|
|
15
|
-
"""
|
|
16
|
-
|
|
17
|
-
from __future__ import annotations
|
|
18
|
-
|
|
19
|
-
from typing import TYPE_CHECKING, Any
|
|
20
|
-
|
|
21
|
-
from matplotlib.axes import Axes
|
|
22
|
-
|
|
23
|
-
if TYPE_CHECKING:
|
|
24
|
-
from matplotlib.backend_bases import KeyEvent
|
|
25
|
-
from matplotlib.figure import Figure
|
|
26
|
-
from matplotlib.text import Text
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
class KeyboardNavigator:
|
|
30
|
-
"""Keyboard navigation handler for interactive plots.
|
|
31
|
-
|
|
32
|
-
: Interactive visualizations are fully keyboard-navigable.
|
|
33
|
-
Provides keyboard shortcuts for panning, zooming, and navigation.
|
|
34
|
-
|
|
35
|
-
Keyboard shortcuts:
|
|
36
|
-
- Arrow keys: Pan left/right/up/down
|
|
37
|
-
- +/-: Zoom in/out
|
|
38
|
-
- Home: Reset to full view
|
|
39
|
-
- Tab: Cycle through subplots
|
|
40
|
-
- Escape: Cancel current operation
|
|
41
|
-
- ?: Show help
|
|
42
|
-
|
|
43
|
-
Args:
|
|
44
|
-
fig: Matplotlib figure
|
|
45
|
-
axes: Matplotlib axes or list of axes
|
|
46
|
-
pan_step: Pan step as fraction of current range (default: 0.1)
|
|
47
|
-
zoom_factor: Zoom factor for +/- keys (default: 1.2)
|
|
48
|
-
|
|
49
|
-
Example:
|
|
50
|
-
>>> import matplotlib.pyplot as plt
|
|
51
|
-
>>> from oscura.visualization.keyboard import KeyboardNavigator
|
|
52
|
-
>>> fig, ax = plt.subplots()
|
|
53
|
-
>>> ax.plot([1, 2, 3], [1, 4, 2])
|
|
54
|
-
>>> nav = KeyboardNavigator(fig, ax)
|
|
55
|
-
>>> nav.connect()
|
|
56
|
-
>>> plt.show()
|
|
57
|
-
|
|
58
|
-
References:
|
|
59
|
-
ACC-003: Keyboard Navigation for Interactive Plots
|
|
60
|
-
"""
|
|
61
|
-
|
|
62
|
-
def __init__(
|
|
63
|
-
self,
|
|
64
|
-
fig: Figure,
|
|
65
|
-
axes: Axes | list[Axes],
|
|
66
|
-
*,
|
|
67
|
-
pan_step: float = 0.1,
|
|
68
|
-
zoom_factor: float = 1.2,
|
|
69
|
-
) -> None:
|
|
70
|
-
"""Initialize keyboard navigator.
|
|
71
|
-
|
|
72
|
-
Args:
|
|
73
|
-
fig: Matplotlib figure
|
|
74
|
-
axes: Single axes or list of axes
|
|
75
|
-
pan_step: Pan step as fraction of range
|
|
76
|
-
zoom_factor: Zoom factor for zoom operations
|
|
77
|
-
"""
|
|
78
|
-
self.fig = fig
|
|
79
|
-
self.axes_list = [axes] if isinstance(axes, Axes) else list(axes)
|
|
80
|
-
self.current_axes_index = 0
|
|
81
|
-
self.pan_step = pan_step
|
|
82
|
-
self.zoom_factor = zoom_factor
|
|
83
|
-
|
|
84
|
-
# Store original limits for reset
|
|
85
|
-
self.original_limits = {}
|
|
86
|
-
for i, ax in enumerate(self.axes_list):
|
|
87
|
-
self.original_limits[i] = {
|
|
88
|
-
"xlim": ax.get_xlim(),
|
|
89
|
-
"ylim": ax.get_ylim(),
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
self._connection_id: int | None = None
|
|
93
|
-
self._help_text: Text | None = None
|
|
94
|
-
|
|
95
|
-
def connect(self) -> None:
|
|
96
|
-
"""Connect keyboard event handler to the figure.
|
|
97
|
-
|
|
98
|
-
: Tab navigates between plot elements.
|
|
99
|
-
Registers keyboard event callback with matplotlib.
|
|
100
|
-
|
|
101
|
-
Example:
|
|
102
|
-
>>> nav.connect()
|
|
103
|
-
>>> # Now keyboard events are handled
|
|
104
|
-
|
|
105
|
-
References:
|
|
106
|
-
ACC-003: Keyboard Navigation for Interactive Plots
|
|
107
|
-
"""
|
|
108
|
-
self._connection_id = self.fig.canvas.mpl_connect("key_press_event", self._on_key) # type: ignore[arg-type]
|
|
109
|
-
self._highlight_active_axes()
|
|
110
|
-
|
|
111
|
-
def disconnect(self) -> None:
|
|
112
|
-
"""Disconnect keyboard event handler.
|
|
113
|
-
|
|
114
|
-
Example:
|
|
115
|
-
>>> nav.disconnect()
|
|
116
|
-
|
|
117
|
-
References:
|
|
118
|
-
ACC-003: Keyboard Navigation for Interactive Plots
|
|
119
|
-
"""
|
|
120
|
-
if self._connection_id is not None:
|
|
121
|
-
self.fig.canvas.mpl_disconnect(self._connection_id)
|
|
122
|
-
self._connection_id = None
|
|
123
|
-
|
|
124
|
-
def _on_key(self, event: KeyEvent) -> None:
|
|
125
|
-
"""Handle keyboard events.
|
|
126
|
-
|
|
127
|
-
: Arrow keys move cursors, Enter selects/activates, Escape closes.
|
|
128
|
-
|
|
129
|
-
Args:
|
|
130
|
-
event: Matplotlib keyboard event
|
|
131
|
-
|
|
132
|
-
References:
|
|
133
|
-
ACC-003: Keyboard Navigation for Interactive Plots
|
|
134
|
-
"""
|
|
135
|
-
if event.key is None:
|
|
136
|
-
return
|
|
137
|
-
|
|
138
|
-
ax = self.axes_list[self.current_axes_index]
|
|
139
|
-
|
|
140
|
-
# Arrow keys: Pan
|
|
141
|
-
if event.key == "left":
|
|
142
|
-
self._pan(ax, dx=-self.pan_step, dy=0)
|
|
143
|
-
elif event.key == "right":
|
|
144
|
-
self._pan(ax, dx=self.pan_step, dy=0)
|
|
145
|
-
elif event.key == "up":
|
|
146
|
-
self._pan(ax, dx=0, dy=self.pan_step)
|
|
147
|
-
elif event.key == "down":
|
|
148
|
-
self._pan(ax, dx=0, dy=-self.pan_step)
|
|
149
|
-
|
|
150
|
-
# Zoom
|
|
151
|
-
elif event.key == "+" or event.key == "=":
|
|
152
|
-
self._zoom(ax, factor=1.0 / self.zoom_factor)
|
|
153
|
-
elif event.key == "-" or event.key == "_":
|
|
154
|
-
self._zoom(ax, factor=self.zoom_factor)
|
|
155
|
-
|
|
156
|
-
# Reset view
|
|
157
|
-
elif event.key == "home":
|
|
158
|
-
self._reset_view(ax)
|
|
159
|
-
|
|
160
|
-
# Tab: Cycle through axes
|
|
161
|
-
elif event.key == "tab":
|
|
162
|
-
self._cycle_axes(reverse=event.key == "shift+tab")
|
|
163
|
-
|
|
164
|
-
# Help
|
|
165
|
-
elif event.key == "?":
|
|
166
|
-
self._show_help()
|
|
167
|
-
|
|
168
|
-
# Escape: Close help or reset
|
|
169
|
-
elif event.key == "escape":
|
|
170
|
-
self._hide_help()
|
|
171
|
-
|
|
172
|
-
else:
|
|
173
|
-
return # Unhandled key
|
|
174
|
-
|
|
175
|
-
self.fig.canvas.draw_idle()
|
|
176
|
-
|
|
177
|
-
def _pan(self, ax: Axes, dx: float, dy: float) -> None:
|
|
178
|
-
"""Pan the axes view.
|
|
179
|
-
|
|
180
|
-
Args:
|
|
181
|
-
ax: Axes to pan
|
|
182
|
-
dx: Horizontal pan as fraction of range
|
|
183
|
-
dy: Vertical pan as fraction of range
|
|
184
|
-
|
|
185
|
-
References:
|
|
186
|
-
ACC-003: Arrow keys move cursors
|
|
187
|
-
"""
|
|
188
|
-
xlim = ax.get_xlim()
|
|
189
|
-
ylim = ax.get_ylim()
|
|
190
|
-
|
|
191
|
-
x_range = xlim[1] - xlim[0]
|
|
192
|
-
y_range = ylim[1] - ylim[0]
|
|
193
|
-
|
|
194
|
-
x_shift = dx * x_range
|
|
195
|
-
y_shift = dy * y_range
|
|
196
|
-
|
|
197
|
-
ax.set_xlim(xlim[0] + x_shift, xlim[1] + x_shift)
|
|
198
|
-
ax.set_ylim(ylim[0] + y_shift, ylim[1] + y_shift)
|
|
199
|
-
|
|
200
|
-
def _zoom(self, ax: Axes, factor: float) -> None:
|
|
201
|
-
"""Zoom the axes view.
|
|
202
|
-
|
|
203
|
-
Args:
|
|
204
|
-
ax: Axes to zoom
|
|
205
|
-
factor: Zoom factor (>1 zooms out, <1 zooms in)
|
|
206
|
-
|
|
207
|
-
References:
|
|
208
|
-
ACC-003: +/- keys zoom in/out
|
|
209
|
-
"""
|
|
210
|
-
xlim = ax.get_xlim()
|
|
211
|
-
ylim = ax.get_ylim()
|
|
212
|
-
|
|
213
|
-
x_center = (xlim[0] + xlim[1]) / 2
|
|
214
|
-
y_center = (ylim[0] + ylim[1]) / 2
|
|
215
|
-
|
|
216
|
-
x_range = (xlim[1] - xlim[0]) * factor
|
|
217
|
-
y_range = (ylim[1] - ylim[0]) * factor
|
|
218
|
-
|
|
219
|
-
ax.set_xlim(x_center - x_range / 2, x_center + x_range / 2)
|
|
220
|
-
ax.set_ylim(y_center - y_range / 2, y_center + y_range / 2)
|
|
221
|
-
|
|
222
|
-
def _reset_view(self, ax: Axes) -> None:
|
|
223
|
-
"""Reset axes to original view.
|
|
224
|
-
|
|
225
|
-
Args:
|
|
226
|
-
ax: Axes to reset
|
|
227
|
-
|
|
228
|
-
References:
|
|
229
|
-
ACC-003: Home resets to full view
|
|
230
|
-
"""
|
|
231
|
-
idx = self.axes_list.index(ax)
|
|
232
|
-
original = self.original_limits[idx]
|
|
233
|
-
ax.set_xlim(original["xlim"])
|
|
234
|
-
ax.set_ylim(original["ylim"])
|
|
235
|
-
|
|
236
|
-
def _cycle_axes(self, reverse: bool = False) -> None:
|
|
237
|
-
"""Cycle through axes with Tab key.
|
|
238
|
-
|
|
239
|
-
Args:
|
|
240
|
-
reverse: Cycle backwards (Shift+Tab)
|
|
241
|
-
|
|
242
|
-
References:
|
|
243
|
-
ACC-003: Tab navigates between plot elements
|
|
244
|
-
"""
|
|
245
|
-
if len(self.axes_list) <= 1:
|
|
246
|
-
return
|
|
247
|
-
|
|
248
|
-
# Remove highlight from current axes
|
|
249
|
-
self._unhighlight_axes(self.axes_list[self.current_axes_index])
|
|
250
|
-
|
|
251
|
-
# Move to next/previous axes
|
|
252
|
-
if reverse:
|
|
253
|
-
self.current_axes_index = (self.current_axes_index - 1) % len(self.axes_list)
|
|
254
|
-
else:
|
|
255
|
-
self.current_axes_index = (self.current_axes_index + 1) % len(self.axes_list)
|
|
256
|
-
|
|
257
|
-
# Highlight new axes
|
|
258
|
-
self._highlight_active_axes()
|
|
259
|
-
|
|
260
|
-
def _highlight_active_axes(self) -> None:
|
|
261
|
-
"""Highlight the currently active axes.
|
|
262
|
-
|
|
263
|
-
: Focus indicators for selected element.
|
|
264
|
-
|
|
265
|
-
References:
|
|
266
|
-
ACC-003: Focus indicators for selected element
|
|
267
|
-
"""
|
|
268
|
-
ax = self.axes_list[self.current_axes_index]
|
|
269
|
-
# Add visual focus indicator
|
|
270
|
-
for spine in ax.spines.values():
|
|
271
|
-
spine.set_edgecolor("red")
|
|
272
|
-
spine.set_linewidth(2)
|
|
273
|
-
|
|
274
|
-
def _unhighlight_axes(self, ax: Axes) -> None:
|
|
275
|
-
"""Remove highlight from axes.
|
|
276
|
-
|
|
277
|
-
Args:
|
|
278
|
-
ax: Axes to unhighlight
|
|
279
|
-
|
|
280
|
-
References:
|
|
281
|
-
ACC-003: Focus indicators for selected element
|
|
282
|
-
"""
|
|
283
|
-
for spine in ax.spines.values():
|
|
284
|
-
spine.set_edgecolor("black")
|
|
285
|
-
spine.set_linewidth(1)
|
|
286
|
-
|
|
287
|
-
def _show_help(self) -> None:
|
|
288
|
-
"""Show keyboard shortcuts help.
|
|
289
|
-
|
|
290
|
-
: ? key shows keyboard shortcuts help.
|
|
291
|
-
|
|
292
|
-
References:
|
|
293
|
-
ACC-003: ? key shows keyboard shortcuts help
|
|
294
|
-
"""
|
|
295
|
-
if self._help_text is not None:
|
|
296
|
-
return # Already showing
|
|
297
|
-
|
|
298
|
-
help_message = """
|
|
299
|
-
Keyboard Navigation Help
|
|
300
|
-
========================
|
|
301
|
-
|
|
302
|
-
Pan:
|
|
303
|
-
←/→/↑/↓ Pan left/right/up/down
|
|
304
|
-
|
|
305
|
-
Zoom:
|
|
306
|
-
+/- Zoom in/out
|
|
307
|
-
|
|
308
|
-
View:
|
|
309
|
-
Home Reset to full view
|
|
310
|
-
|
|
311
|
-
Navigation:
|
|
312
|
-
Tab Next subplot
|
|
313
|
-
Shift+Tab Previous subplot
|
|
314
|
-
|
|
315
|
-
Help:
|
|
316
|
-
? Show this help
|
|
317
|
-
Esc Close help
|
|
318
|
-
|
|
319
|
-
Press Esc to close this help.
|
|
320
|
-
"""
|
|
321
|
-
# Add text box to figure
|
|
322
|
-
self._help_text = self.fig.text(
|
|
323
|
-
0.5,
|
|
324
|
-
0.5,
|
|
325
|
-
help_message,
|
|
326
|
-
ha="center",
|
|
327
|
-
va="center",
|
|
328
|
-
fontsize=10,
|
|
329
|
-
family="monospace",
|
|
330
|
-
bbox={
|
|
331
|
-
"boxstyle": "round",
|
|
332
|
-
"facecolor": "wheat",
|
|
333
|
-
"alpha": 0.95,
|
|
334
|
-
"edgecolor": "black",
|
|
335
|
-
"linewidth": 2,
|
|
336
|
-
},
|
|
337
|
-
zorder=1000,
|
|
338
|
-
)
|
|
339
|
-
self.fig.canvas.draw_idle()
|
|
340
|
-
|
|
341
|
-
def _hide_help(self) -> None:
|
|
342
|
-
"""Hide keyboard shortcuts help.
|
|
343
|
-
|
|
344
|
-
: Escape closes modals/menus.
|
|
345
|
-
|
|
346
|
-
References:
|
|
347
|
-
ACC-003: Escape closes modals/menus
|
|
348
|
-
"""
|
|
349
|
-
if self._help_text is not None:
|
|
350
|
-
self._help_text.remove()
|
|
351
|
-
self._help_text = None
|
|
352
|
-
self.fig.canvas.draw_idle()
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
def enable_keyboard_navigation(
|
|
356
|
-
fig: Figure,
|
|
357
|
-
axes: Axes | list[Axes] | None = None,
|
|
358
|
-
**kwargs: Any,
|
|
359
|
-
) -> KeyboardNavigator:
|
|
360
|
-
"""Enable keyboard navigation for a figure.
|
|
361
|
-
|
|
362
|
-
Convenience function to create and connect a KeyboardNavigator.
|
|
363
|
-
|
|
364
|
-
Args:
|
|
365
|
-
fig: Matplotlib figure
|
|
366
|
-
axes: Axes to navigate (default: all axes in figure)
|
|
367
|
-
**kwargs: Additional arguments passed to KeyboardNavigator
|
|
368
|
-
|
|
369
|
-
Returns:
|
|
370
|
-
Connected KeyboardNavigator instance
|
|
371
|
-
|
|
372
|
-
Example:
|
|
373
|
-
>>> import matplotlib.pyplot as plt
|
|
374
|
-
>>> from oscura.visualization.keyboard import enable_keyboard_navigation
|
|
375
|
-
>>> fig, ax = plt.subplots()
|
|
376
|
-
>>> ax.plot([1, 2, 3], [1, 4, 2])
|
|
377
|
-
>>> nav = enable_keyboard_navigation(fig)
|
|
378
|
-
>>> plt.show()
|
|
379
|
-
|
|
380
|
-
References:
|
|
381
|
-
ACC-003: Keyboard Navigation for Interactive Plots
|
|
382
|
-
"""
|
|
383
|
-
if axes is None:
|
|
384
|
-
axes = fig.get_axes()
|
|
385
|
-
|
|
386
|
-
navigator = KeyboardNavigator(fig, axes, **kwargs)
|
|
387
|
-
navigator.connect()
|
|
388
|
-
return navigator
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
__all__ = [
|
|
392
|
-
"KeyboardNavigator",
|
|
393
|
-
"enable_keyboard_navigation",
|
|
394
|
-
]
|