better-rtplot 0.2.2__tar.gz → 0.3.0__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: better-rtplot
3
- Version: 0.2.2
3
+ Version: 0.3.0
4
4
  Summary:
5
5
  License: GPL V3.0
6
6
  Author: jmontp
@@ -16,15 +16,11 @@ Provides-Extra: browser
16
16
  Provides-Extra: server
17
17
  Requires-Dist: aiohttp (>=3.9.0) ; extra == "browser"
18
18
  Requires-Dist: numpy (>=1.23.5)
19
- Requires-Dist: pandas (>=1.5.3) ; extra == "server" or extra == "browser"
20
- Requires-Dist: pyarrow (>=11.0.0) ; extra == "server" or extra == "browser"
21
19
  Requires-Dist: pyqtgraph (>=0.13.0) ; extra == "server"
22
20
  Requires-Dist: pyside6 (>6.4.0) ; extra == "server"
23
21
  Requires-Dist: pyzmq (>=25.0.0)
24
22
  Description-Content-Type: text/markdown
25
23
 
26
- ![Logo of the project](https://github.com/jmontp/rtplot/blob/master/.images/signature-stationery.png)
27
-
28
24
  # rtplot — real-time plotting over ZMQ
29
25
 
30
26
  **rtplot** lets a Python script push live data to a plot window — locally, or
@@ -37,25 +33,114 @@ Typical use: a robot or data-acquisition script runs on a Raspberry Pi or
37
33
  microcontroller host, and you watch live signals and tweak gains from a
38
34
  laptop on the same network.
39
35
 
36
+ **Looking for runnable examples?** Every subfolder in
37
+ [`examples/`](examples/) is a small, self-contained script with its own
38
+ `README.md` and a static `snapshot.html` you can open in a browser to
39
+ preview what the plot looks like without running anything.
40
+
40
41
  ---
41
42
 
42
43
  ## Table of contents
43
44
 
45
+ - [How it works](#how-it-works)
46
+ - [Your first plot, step by step](#your-first-plot-step-by-step)
44
47
  - [Highlights](#highlights)
45
48
  - [Install](#install)
46
- - [60-second quickstart](#60-second-quickstart)
47
49
  - [Interactive controls](#interactive-controls)
48
- - [Reading controls from Python](#reading-controls-from-python)
49
- - [Pushing values into displays](#pushing-values-into-displays)
50
- - [Element reference](#element-reference)
51
50
  - [Plot configuration](#plot-configuration)
52
51
  - [Sending data](#sending-data)
53
- - [Saving data](#saving-data)
52
+ - [Static HTML snapshots](#static-html-snapshots)
53
+ - [Browser UI features](#browser-ui-features)
54
54
  - [Networking modes](#networking-modes)
55
55
  - [Viewing the plot from another device](#viewing-the-plot-from-another-device)
56
56
  - [Performance tuning](#performance-tuning)
57
57
  - [CLI reference](#cli-reference)
58
- - [Examples](#examples)
58
+ - [Client API reference](#client-api-reference)
59
+ - [Examples gallery](#examples-gallery)
60
+
61
+ ---
62
+
63
+ ## How it works
64
+
65
+ rtplot has two pieces that run independently:
66
+
67
+ - The **server** is a small program that shows plots in a web browser.
68
+ You start it once (or download it as a standalone Windows / Linux /
69
+ macOS binary from the
70
+ [Releases page](https://github.com/jmontp/rtplot/releases)) and it
71
+ sits there waiting for data.
72
+ - The **client** is a tiny Python library you import from your own
73
+ script. Calling `client.send_array(value)` in your loop makes a new
74
+ data point appear on the server's plot.
75
+
76
+ Picture it like this:
77
+
78
+ ```
79
+ ┌──────────────────────┐ ┌──────────────────────┐
80
+ │ Your Python script │ │ rtplot-server │
81
+ │ │ │ │
82
+ │ from rtplot import │── ZMQ ───▶ │ ┌────────────────┐ │
83
+ │ client │ :5555 │ │ browser tab at │ │
84
+ │ │ │ │ localhost:8050 │ │
85
+ │ client.send_array() │ ◀── ZMQ ── │ └────────────────┘ │
86
+ │ │ :5556 │ │
87
+ └──────────────────────┘ └──────────────────────┘
88
+ (the client lib) (the exe or module)
89
+ ```
90
+
91
+ Data flows from your script to the server on **ZMQ port 5555**. When
92
+ the server runs the interactive-controls feature, button clicks and
93
+ slider values flow **back** to your script on port **5556**. The
94
+ server also hosts an HTTP page on port **8050** that any browser can
95
+ open to see the live plot.
96
+
97
+ The two pieces don't have to run on the same machine. Running rtplot
98
+ on a Raspberry Pi and watching the plots from your laptop is the same
99
+ code — just tell the client where the server is (or vice versa).
100
+
101
+ ---
102
+
103
+ ## Your first plot, step by step
104
+
105
+ **Step 0** — install rtplot with the browser server bundled:
106
+
107
+ ```bash
108
+ pip install "better-rtplot[browser]"
109
+ ```
110
+
111
+ **Step 1** — start the server. In **one** terminal:
112
+
113
+ ```bash
114
+ python -m rtplot.server_browser
115
+ ```
116
+
117
+ It prints a URL like `http://localhost:8050`. Open that in a browser.
118
+ The page is blank for now — no data has been sent yet, which is fine.
119
+
120
+ **Step 2** — write and run your script. In **another** terminal, save
121
+ this as `my_plot.py`:
122
+
123
+ ```python
124
+ from rtplot import client
125
+ import time
126
+
127
+ client.local_plot() # point at the server on this machine
128
+ client.initialize_plots(["my signal"]) # declare one plot with one trace
129
+
130
+ for i in range(1000):
131
+ client.send_array(i * 0.01) # ship one sample per iteration
132
+ time.sleep(0.01)
133
+ ```
134
+
135
+ Run it: `python my_plot.py`.
136
+
137
+ **Step 3** — switch back to the browser tab. A rising-line plot is
138
+ now drawing itself in real time.
139
+
140
+ That's everything you need to get started. The rest of this README is
141
+ a reference for options, styling, interactive controls, and remote
142
+ networking, plus a [gallery of example scripts](examples/) you can
143
+ preview as static snapshots before running them yourself.
59
144
 
60
145
  ---
61
146
 
@@ -71,31 +156,35 @@ laptop on the same network.
71
156
  works over SSH port forwarding out of the box.
72
157
  - **Remote-friendly.** Either the sender or the plot host can bind —
73
158
  pick whichever fits your network. Works across LAN, WSL, and SSH
74
- tunnels.
159
+ tunnels. The browser UI has live Bind / Connect buttons so you can
160
+ retarget without restarting the server.
75
161
  - **Plot config lives with the data.** The sender declares the plot
76
162
  layout, so a Pi running your experiment owns the look of its own
77
163
  dashboards.
78
- - **Interactive controls.** Declare buttons, sliders, dials,
79
- numeric/text displays in the same `initialize_plots` call. Poll from
80
- your tight loop; no threads, no callbacks.
81
- - **Save to Parquet** with a single button click or `client.save_plot()`
82
- call.
164
+ - **Interactive controls.** Declare buttons, sliders, dials, and
165
+ numeric / text displays in the same `initialize_plots` call. Poll
166
+ from your tight loop; no threads, no callbacks.
167
+ - **Static HTML snapshots.** `client.save_snapshot("out.html")` writes
168
+ a self-contained HTML file with the current trace data and uPlot
169
+ inlined. Perfect for commit-to-repo gallery previews or emailing a
170
+ "here's what I saw" artifact.
83
171
 
84
172
  ---
85
173
 
86
174
  ## Install
87
175
 
88
- Install rtplot with the server bundle this is the normal path and
89
- gets you everything:
176
+ ### Normal pathpip
177
+
178
+ Install rtplot with the server bundle:
90
179
 
91
180
  ```bash
92
181
  pip install "better-rtplot[browser]"
93
182
  ```
94
183
 
95
- This pulls `aiohttp` (for serving the plot UI) plus `pandas` + `pyarrow`
96
- (for saving runs to Parquet). If you only need the sender side — your
97
- script pushes data to someone else's plot host and you don't run a
98
- server locally — you can install the client-only minimum instead:
184
+ This pulls `aiohttp` (for serving the plot UI). If you only need the
185
+ sender side your script pushes data to someone else's plot host and
186
+ you don't run a server locally you can install the client-only
187
+ minimum:
99
188
 
100
189
  ```bash
101
190
  pip install better-rtplot
@@ -107,36 +196,27 @@ clear error telling you to add the `[browser]` extra.
107
196
  WSL users: nothing extra needed. The plot window is served by HTTP, so
108
197
  just open the URL rtplot prints in your Windows browser.
109
198
 
110
- ---
111
-
112
- ## 60-second quickstart
113
-
114
- **Terminal 1 — start the plot server:**
115
-
116
- ```bash
117
- python -m rtplot.server_browser
118
- ```
119
-
120
- It prints a URL like `http://localhost:8050` — open that in your
121
- browser. The page stays blank until a client sends a plot config.
122
-
123
- **Terminal 2 — send data:**
124
-
125
- ```python
126
- from rtplot import client
127
- import numpy as np, time
199
+ ### No-Python path — prebuilt binary
128
200
 
129
- client.local_plot() # send to the server on 127.0.0.1
130
- client.initialize_plots(["sin", "cos"]) # one plot with two named traces
201
+ Every tagged release on GitHub ships a standalone `rtplot-server`
202
+ binary built for **windows-x64**, **linux-x86_64**, and
203
+ **macos-arm64**. Download from the
204
+ [Releases page](https://github.com/jmontp/rtplot/releases) and run
205
+ directly — no Python install needed on that machine.
131
206
 
132
- for i in range(10000):
133
- t = i * 0.01
134
- client.send_array([np.sin(t), np.cos(t)])
135
- time.sleep(0.01)
136
- ```
207
+ On Windows the binary opens a small Tk status window
208
+ (`rtplot/server_browser_gui.py`) that shows the listening URL, ZMQ
209
+ status, an optional demo sender for smoke-testing end-to-end
210
+ connectivity, and a collapsable log panel.
211
+ Senders still need Python + `pip install better-rtplot`; the binary
212
+ only replaces the *server* side, which is the part most people don't
213
+ want to set up on a plot-viewing machine.
137
214
 
138
- That's it. The browser tab you opened will start drawing the two
139
- traces in real time.
215
+ | Platform | Asset name |
216
+ |---|---|
217
+ | Windows | `rtplot-server-<version>-windows-x64.exe` |
218
+ | Linux | `rtplot-server-<version>-linux-x86_64.tar.gz` |
219
+ | macOS (Apple Silicon) | `rtplot-server-<version>-macos-arm64.tar.gz` |
140
220
 
141
221
  ---
142
222
 
@@ -223,19 +303,28 @@ server and rebroadcast to every connected browser at ~30 Hz.
223
303
 
224
304
  | Type | Purpose | Notable fields |
225
305
  |---|---|---|
226
- | `button` | Fires a discrete event when clicked | `id`, `label` |
227
- | `slider` | Scalar input via horizontal range | `id`, `label`, `min`, `max`, `value`, `step`, `format` |
228
- | `dial` | Scalar input via rotational drag | same as slider, plus `sensitivity` (full turns per range sweep; default `1.0`) |
229
- | `display` | Read-only numeric readout | `id`, `label`, `format` |
230
- | `text` | Read-only text field (prompts, status) | `id`, `label`, `value` |
306
+ | `button` | Fires a discrete event when clicked | `id`, `label`, `height` |
307
+ | `slider` | Scalar input via horizontal range | `id`, `label`, `min`, `max`, `value`, `step`, `format`, `height` |
308
+ | `dial` | Scalar input via vertical drag on a circular indicator | same as slider, plus `sensitivity` (fraction of value range per rotation; default `1.0`) |
309
+ | `display` | Read-only numeric readout | `id`, `label`, `format`, `height` |
310
+ | `text` | Read-only text field (prompts, status) | `id`, `label`, `value`, `height` |
231
311
 
232
312
  Slider and dial widgets both render as **`[widget] [−] [number input] [+]`**,
233
313
  so you can drag, type a value directly, or nudge by `step`. The dial
234
- accepts "round and round" circular drag — each full rotation walks the
235
- value through `(max min) × sensitivity`, so `sensitivity: 0.25` gives
236
- you four rotations per sweep for fine control.
314
+ uses a vertical pointer drag — drag up to increase — and the
315
+ `sensitivity` field controls how many units of value change one full
316
+ rotation covers. `sensitivity: 1.0` (default) maps one rotation to the
317
+ full `(max − min)` range; `sensitivity: 0.25` needs four rotations to
318
+ sweep the range for finer control.
237
319
 
238
- The `format` field accepts Python-style `{:.Nf}` strings (e.g. `"{:.2f}"`).
320
+ The `format` field accepts Python-style `{:.Nf}` strings (e.g.
321
+ `"{:.2f}"`). The `height` field is an optional multiplier on the
322
+ standard row height (default `1`) — e.g. `"height": 2` gives a dial
323
+ that's twice as tall (and therefore twice as wide), or a button with
324
+ twice the click target.
325
+
326
+ See [`examples/03_interactive_controls/`](examples/03_interactive_controls/)
327
+ for a runnable walkthrough of the full control palette.
239
328
 
240
329
  ---
241
330
 
@@ -243,10 +332,10 @@ The `format` field accepts Python-style `{:.Nf}` strings (e.g. `"{:.2f}"`).
243
332
 
244
333
  Each entry in `initialize_plots` is one of:
245
334
 
246
- - an **integer** — `client.initialize_plots(3)` → one plot with 3 anonymous
247
- traces
248
- - a **string** — `client.initialize_plots("torque")` → one plot with one
249
- named trace
335
+ - an **integer** — `client.initialize_plots(3)` → one plot with 3
336
+ anonymous traces
337
+ - a **string** — `client.initialize_plots("torque")` → one plot with
338
+ one named trace
250
339
  - a **list of strings** — one plot, one trace per name
251
340
  - a **list of lists of strings** — one plot per sublist
252
341
  - a **dict** — one plot, with full styling options (below)
@@ -258,20 +347,18 @@ A styled plot dict accepts any of:
258
347
  |---|---|
259
348
  | `names` | **Required.** List of trace names. |
260
349
  | `colors` | List of per-trace colors. Single letter (`r g b c m y k w`) or any CSS color string. |
261
- | `line_style` | `"-"` for dashed, `""` (or anything else) for solid, per trace. |
350
+ | `line_style` | Per-trace dash style. `"-"` means dashed; anything else is solid. |
262
351
  | `line_width` | Per-trace line width in pixels. |
263
352
  | `title` | Plot title. |
264
353
  | `xlabel` / `ylabel` | Axis labels. |
265
354
  | `yrange` | `[ymin, ymax]` — pins the Y axis and significantly speeds up rendering. |
266
355
  | `xrange` | Integer number of samples visible at once (default 200). |
356
+ | `height` | Per-plot height multiplier (default `1.0`). Use `2` for a plot that's twice as tall as the others in the layout. |
267
357
 
268
358
  Special row entries (not plots themselves):
269
359
 
270
360
  - `{"controls": [...]}` — a row of interactive controls (see
271
361
  [Interactive controls](#interactive-controls))
272
- - `{"non_plot_labels": ["name1", "name2"]}` — extra scalar names that ride
273
- along with `send_array` and get saved into the output Parquet file, but
274
- aren't rendered as traces
275
362
 
276
363
  ---
277
364
 
@@ -290,38 +377,58 @@ without dropping frames.
290
377
 
291
378
  ---
292
379
 
293
- ## Saving data
380
+ ## Static HTML snapshots
294
381
 
295
- The server saves every sample it has received since the latest
296
- `initialize_plots` call to a Parquet file, including any
297
- `non_plot_labels` data that rode along with your normal data.
382
+ rtplot deliberately doesn't persist runs as a file format it's a
383
+ live-plotting tool, not a data logger. When you do want a reproducible
384
+ artifact of what the plot looked like at a given moment, call:
298
385
 
299
- Trigger a save from either side:
386
+ ```python
387
+ client.save_snapshot("preview.html", animate=True)
388
+ ```
300
389
 
301
- - **Browser UI:** click the **Save Plot** button.
302
- - **Python:** `client.save_plot("my_run")`
390
+ It writes a self-contained HTML file with uPlot JS + CSS inlined and
391
+ the current window of trace data embedded. Opens offline in any
392
+ browser, around 65 KB. Control widgets aren't captured — only the
393
+ plot portion — so the snapshot is the right artifact to commit to a
394
+ repo as a visual-regression baseline, attach to an email, or drop
395
+ into a GitHub Pages gallery. With `animate=True` the snapshot embeds
396
+ a small replay loop so the trace keeps scrolling (nicer for gallery
397
+ previews).
303
398
 
304
- Control where things get written:
399
+ The `server_url` argument defaults to `http://localhost:8050`; set it
400
+ explicitly when snapshotting a remote server or one running on a
401
+ non-default `--port`.
305
402
 
306
- ```bash
307
- python -m rtplot.server_browser -sd ./saved_plots -sn experiment1
308
- ```
403
+ ---
309
404
 
310
- - `-sd` / `--save-dir` — target directory
311
- - `-sn` / `--save-name` — filename prefix (a timestamp is always appended)
405
+ ## Browser UI features
312
406
 
313
- ### Save non-plot signals alongside the plotted ones
407
+ The browser tab isn't just a passive plot the header bar and a
408
+ hamburger-menu settings panel give you live control over the server
409
+ without restarting it.
314
410
 
315
- ```python
316
- client.initialize_plots([
317
- {"names": ["hip_angle", "knee_angle"]},
318
- {"non_plot_labels": ["battery", "cpu_temp", "loop_latency"]},
319
- ])
320
- ```
411
+ **Header controls**
412
+
413
+ | Element | What it does |
414
+ |---|---|
415
+ | Status pill | Live data rate + render rate (e.g. `Data 480 Hz · Render 60 Hz`). Turns red when the server marks the stream unhealthy. |
416
+ | `ZMQ …` indicator | Shows whether the server is currently **binding** (`ZMQ bind *:5555`) or **connecting outbound** (`ZMQ → host:port`). |
417
+ | IP input | Type a `host[:port]` to retarget before clicking **Connect**. |
418
+ | **Connect** / **Bind** buttons | Flip the server between *connect-to-a-sender* and *bind-and-wait* modes at runtime. The active mode is highlighted; the other is clickable. |
419
+ | WebSocket status | `connected` / `disconnected, retrying…` — for the browser-to-server link, not the ZMQ link. |
420
+ | **☰** menu button | Opens the Settings panel (below). |
421
+
422
+ **Settings panel (☰)**
423
+
424
+ | Setting | Meaning |
425
+ |---|---|
426
+ | UI font scale | 0.7× – 2.0× multiplier on every piece of browser-side text. Good for demos, projectors, and high-DPI screens. |
427
+ | Visible samples per plot | Overrides the declared `xrange` — lets a viewer zoom out or in without touching the sender script. |
428
+ | Max plot refresh rate | Caps repaints at N Hz. The panel reports the monitor's measured refresh rate via `requestAnimationFrame` calibration, so you know the ceiling. Leave blank to use the monitor Hz as the cap. |
321
429
 
322
- Send `battery`, `cpu_temp` and `loop_latency` as extra rows after the
323
- plotted traces in each `send_array` call; they won't be drawn but they
324
- will land in the Parquet file.
430
+ All settings are persisted in `localStorage`, so a refresh keeps your
431
+ preferences. The **Reset to defaults** button clears them.
325
432
 
326
433
  ---
327
434
 
@@ -329,7 +436,8 @@ will land in the Parquet file.
329
436
 
330
437
  rtplot uses ZMQ, so either the sender or the plot host can be the one
331
438
  that *binds* a socket. Pick whichever works for your network and
332
- firewalls.
439
+ firewalls. You can also flip modes from the browser UI's **Bind** /
440
+ **Connect** buttons without restarting the server.
333
441
 
334
442
  **Mode A — plot host binds, sender connects** *(typical for lab laptops)*
335
443
 
@@ -491,12 +599,15 @@ If you start running out of frames, try these, in roughly this order:
491
599
  renderer skip autoscaling work and gives the single biggest win.
492
600
  2. **Batch your samples.** Pass a 2-D numpy array to `send_array` so N
493
601
  samples ship per call.
494
- 3. **Shrink the window.** Fewer pixels to redraw per frame.
495
- 4. **Reduce `line_width`.** Thicker lines cost more to rasterize.
496
- 5. **Use the `-s N` / `--skip N` server flag** to push every Nth sample
602
+ 3. **Cap the plot refresh rate** from the browser's ☰ Settings menu.
603
+ The ring buffers keep accumulating samples; only the repaint rate
604
+ is throttled.
605
+ 4. **Shrink the window.** Fewer pixels to redraw per frame.
606
+ 5. **Reduce `line_width`.** Thicker lines cost more to rasterize.
607
+ 6. **Use the `-n N` / `--skip N` server flag** to push every Nth sample
497
608
  batch to the browser instead of every one. Add `-a` / `--adaptable`
498
609
  to let the server tune `N` to your data rate automatically.
499
- 6. **Increase `xrange`.** Counterintuitively, a longer visible history
610
+ 7. **Increase `xrange`.** Counterintuitively, a longer visible history
500
611
  can be cheaper than a short one because the browser ring-buffers the
501
612
  data and only replaces the tail on each push.
502
613
 
@@ -508,7 +619,7 @@ If you start running out of frames, try these, in roughly this order:
508
619
 
509
620
  | Flag | Default | Meaning |
510
621
  |---|---|---|
511
- | `-p HOST[:PORT]` | (bind) | Connect to a sender at this address instead of binding |
622
+ | `-p HOST[:PORT]` / `--pi_ip` | (bind) | Connect to a sender at this address instead of binding |
512
623
  | `--host HOST` | `0.0.0.0` | HTTP bind interface |
513
624
  | `--port N` | `8050` | HTTP port |
514
625
  | `--no-browser` | off | Don't try to open a browser on startup |
@@ -517,22 +628,55 @@ If you start running out of frames, try these, in roughly this order:
517
628
  | `-a` / `--adaptable` | off | Auto-tune skip rate to data rate |
518
629
  | `-c` / `--column` | row | Lay plots out in columns instead of rows |
519
630
  | `-d` / `--debug` | off | Extra debug logging |
520
- | `-sd DIR` / `--save-dir DIR` | cwd | Where to write `.parquet` saves |
521
- | `-sn NAME` / `--save-name NAME` | — | Prefix for saved filenames |
522
631
 
523
632
  ---
524
633
 
525
- ## Examples
634
+ ## Client API reference
526
635
 
527
- - [`rtplot/example_code.py`](rtplot/example_code.py) a walk through
528
- every `initialize_plots` signature, plus a controls demo at the bottom.
529
- - [`rtplot/interactive_test.py`](rtplot/interactive_test.py) — a guided
530
- end-to-end test that walks you through clicking buttons, dragging
531
- sliders, typing into the number input, using the ± nudge arrows, and
532
- spinning the dial. Good for smoke-testing a fresh install.
636
+ Every function below is imported from `rtplot.client`:
533
637
 
534
- ```bash
535
- python -m rtplot.server_browser &
536
- python -m rtplot.interactive_test
537
- ```
638
+ | Function | Purpose |
639
+ |---|---|
640
+ | `local_plot()` | Point the client at a server on `127.0.0.1:5555`. Shorthand for `configure_ip("127.0.0.1")`. |
641
+ | `plot_to_neurobionics_tv()` | Point at the lab's wall-display host (`141.212.77.23:5555`). |
642
+ | `configure_ip(ip)` | Connect to a server at `ip`, `host:port`, or a full `tcp://host:port` string. Also connects the control return-channel socket to `port+1`. |
643
+ | `configure_port(port)` | Rebind the local publisher to a different port (for senders running in bind mode). |
644
+ | `initialize_plots(desc)` | Declare the plot layout. Accepts int, str, dict, list-of-strings, list-of-lists, or list-of-dicts (see [Plot configuration](#plot-configuration)). |
645
+ | `send_array(A)` | Push one or more samples. Accepts float, list, 1-D numpy array, or 2-D `(num_traces, N)` numpy array. |
646
+ | `set_display(id, value)` | Update a `display` (numeric) or `text` (string) element. |
647
+ | `poll_controls()` | Drain the return channel non-blocking; returns `ControlState(values, buttons)`. |
648
+ | `save_snapshot(path, server_url=None, animate=False)` | Download a self-contained static HTML snapshot of the current plot to `path`. |
649
+
650
+ ---
651
+
652
+ ## Examples gallery
653
+
654
+ The [`examples/`](examples/) directory is a small, self-contained
655
+ gallery. Each folder has a `run.py` you can copy, a `README.md` that
656
+ explains what the code is teaching, and a pre-generated `snapshot.html`
657
+ you can open in a browser to see what the live plot looked like —
658
+ no server, no Python, no network required.
659
+
660
+ | Example | What it teaches |
661
+ |---|---|
662
+ | [`examples/01_hello_world/`](examples/01_hello_world/) | The minimum three client calls: `local_plot`, `initialize_plots`, `send_array`. One plot, one sine wave. |
663
+ | [`examples/02_multiple_subplots/`](examples/02_multiple_subplots/) | Multi-plot layouts, multi-trace plots, per-plot styling, flat-list `send_array`. Three subplots, four traces. |
664
+ | [`examples/03_interactive_controls/`](examples/03_interactive_controls/) | Buttons, sliders, dials, and display boxes that drive your Python loop live. |
665
+
666
+ To run any example, start the server in one terminal and `python run.py`
667
+ in another from inside the example's folder — see
668
+ [`examples/README.md`](examples/README.md) for details and the
669
+ regenerate-all-snapshots one-liner.
670
+
671
+ For an end-to-end smoke test of the full control palette (the gallery's
672
+ snapshots can't capture interactive widget state),
673
+ [`rtplot/interactive_test.py`](rtplot/interactive_test.py) walks a
674
+ human through clicking each button, dragging the slider to specific
675
+ values, typing into the number input, using the ± nudge arrows, and
676
+ spinning the dial:
677
+
678
+ ```bash
679
+ python -m rtplot.server_browser &
680
+ python -m rtplot.interactive_test
681
+ ```
538
682