plotui 0.4.1__tar.gz → 0.4.2__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.
Files changed (36) hide show
  1. {plotui-0.4.1 → plotui-0.4.2}/Cargo.lock +9 -9
  2. {plotui-0.4.1 → plotui-0.4.2}/Cargo.toml +6 -6
  3. {plotui-0.4.1 → plotui-0.4.2}/PKG-INFO +10 -3
  4. {plotui-0.4.1 → plotui-0.4.2}/README.md +9 -1
  5. {plotui-0.4.1 → plotui-0.4.2}/crates/plotui-bind/README.md +9 -1
  6. {plotui-0.4.1 → plotui-0.4.2}/crates/plotui-core/README.md +9 -1
  7. {plotui-0.4.1 → plotui-0.4.2}/crates/plotui-protocol/README.md +9 -1
  8. plotui-0.4.2/crates/plotui-term/README.md +228 -0
  9. {plotui-0.4.1 → plotui-0.4.2}/pyproject.toml +14 -6
  10. plotui-0.4.2/python/plotui/_cli.py +25 -0
  11. {plotui-0.4.1 → plotui-0.4.2}/python/plotui/textual.py +7 -5
  12. plotui-0.4.1/crates/plotui-term/README.md +0 -220
  13. {plotui-0.4.1 → plotui-0.4.2}/LICENSE +0 -0
  14. {plotui-0.4.1 → plotui-0.4.2}/crates/plotui-bind/Cargo.toml +0 -0
  15. {plotui-0.4.1 → plotui-0.4.2}/crates/plotui-bind/src/lib.rs +0 -0
  16. {plotui-0.4.1 → plotui-0.4.2}/crates/plotui-core/Cargo.toml +0 -0
  17. {plotui-0.4.1 → plotui-0.4.2}/crates/plotui-core/src/font.rs +0 -0
  18. {plotui-0.4.1 → plotui-0.4.2}/crates/plotui-core/src/hershey.rs +0 -0
  19. {plotui-0.4.1 → plotui-0.4.2}/crates/plotui-core/src/lib.rs +0 -0
  20. {plotui-0.4.1 → plotui-0.4.2}/crates/plotui-core/src/ticks.rs +0 -0
  21. {plotui-0.4.1 → plotui-0.4.2}/crates/plotui-core/tests/render.rs +0 -0
  22. {plotui-0.4.1 → plotui-0.4.2}/crates/plotui-protocol/Cargo.toml +0 -0
  23. {plotui-0.4.1 → plotui-0.4.2}/crates/plotui-protocol/src/diacritics.rs +0 -0
  24. {plotui-0.4.1 → plotui-0.4.2}/crates/plotui-protocol/src/lib.rs +0 -0
  25. {plotui-0.4.1 → plotui-0.4.2}/crates/plotui-protocol/tests/protocol.rs +0 -0
  26. {plotui-0.4.1 → plotui-0.4.2}/crates/plotui-py/Cargo.toml +0 -0
  27. {plotui-0.4.1 → plotui-0.4.2}/crates/plotui-py/src/lib.rs +0 -0
  28. {plotui-0.4.1 → plotui-0.4.2}/crates/plotui-term/Cargo.toml +0 -0
  29. {plotui-0.4.1 → plotui-0.4.2}/crates/plotui-term/src/compose.rs +0 -0
  30. {plotui-0.4.1 → plotui-0.4.2}/crates/plotui-term/src/detect.rs +0 -0
  31. {plotui-0.4.1 → plotui-0.4.2}/crates/plotui-term/src/ids.rs +0 -0
  32. {plotui-0.4.1 → plotui-0.4.2}/crates/plotui-term/src/lib.rs +0 -0
  33. {plotui-0.4.1 → plotui-0.4.2}/crates/plotui-term/src/policy.rs +0 -0
  34. {plotui-0.4.1 → plotui-0.4.2}/crates/plotui-term/src/tmux.rs +0 -0
  35. {plotui-0.4.1 → plotui-0.4.2}/crates/plotui-term/tests/term.rs +0 -0
  36. {plotui-0.4.1 → plotui-0.4.2}/python/plotui/__init__.py +0 -0
@@ -1117,7 +1117,7 @@ checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
1117
1117
 
1118
1118
  [[package]]
1119
1119
  name = "plotui"
1120
- version = "0.4.1"
1120
+ version = "0.4.2"
1121
1121
  dependencies = [
1122
1122
  "clap",
1123
1123
  "crossterm",
@@ -1129,18 +1129,18 @@ dependencies = [
1129
1129
 
1130
1130
  [[package]]
1131
1131
  name = "plotui-bind"
1132
- version = "0.4.1"
1132
+ version = "0.4.2"
1133
1133
  dependencies = [
1134
1134
  "plotui-core",
1135
1135
  ]
1136
1136
 
1137
1137
  [[package]]
1138
1138
  name = "plotui-core"
1139
- version = "0.4.1"
1139
+ version = "0.4.2"
1140
1140
 
1141
1141
  [[package]]
1142
1142
  name = "plotui-ffi"
1143
- version = "0.4.1"
1143
+ version = "0.4.2"
1144
1144
  dependencies = [
1145
1145
  "cbindgen",
1146
1146
  "plotui-bind",
@@ -1151,7 +1151,7 @@ dependencies = [
1151
1151
 
1152
1152
  [[package]]
1153
1153
  name = "plotui-protocol"
1154
- version = "0.4.1"
1154
+ version = "0.4.2"
1155
1155
  dependencies = [
1156
1156
  "base64",
1157
1157
  "flate2",
@@ -1160,7 +1160,7 @@ dependencies = [
1160
1160
 
1161
1161
  [[package]]
1162
1162
  name = "plotui-py"
1163
- version = "0.4.1"
1163
+ version = "0.4.2"
1164
1164
  dependencies = [
1165
1165
  "numpy",
1166
1166
  "plotui-bind",
@@ -1172,7 +1172,7 @@ dependencies = [
1172
1172
 
1173
1173
  [[package]]
1174
1174
  name = "plotui-ratatui"
1175
- version = "0.4.1"
1175
+ version = "0.4.2"
1176
1176
  dependencies = [
1177
1177
  "crossterm",
1178
1178
  "plotui-core",
@@ -1183,7 +1183,7 @@ dependencies = [
1183
1183
 
1184
1184
  [[package]]
1185
1185
  name = "plotui-term"
1186
- version = "0.4.1"
1186
+ version = "0.4.2"
1187
1187
  dependencies = [
1188
1188
  "plotui-core",
1189
1189
  "plotui-protocol",
@@ -1192,7 +1192,7 @@ dependencies = [
1192
1192
 
1193
1193
  [[package]]
1194
1194
  name = "plotui-wasm"
1195
- version = "0.4.1"
1195
+ version = "0.4.2"
1196
1196
  dependencies = [
1197
1197
  "plotui-bind",
1198
1198
  "plotui-core",
@@ -3,7 +3,7 @@ resolver = "2"
3
3
  members = ["crates/plotui-core", "crates/plotui-protocol", "crates/plotui-term", "crates/plotui-bind", "crates/plotui-py"]
4
4
 
5
5
  [workspace.package]
6
- version = "0.4.1"
6
+ version = "0.4.2"
7
7
  edition = "2021"
8
8
  license = "MIT"
9
9
  repository = "https://github.com/sebaheg/plotui"
@@ -14,11 +14,11 @@ keywords = ["plot", "terminal", "tui", "kitty", "visualization"]
14
14
  categories = ["visualization", "command-line-utilities"]
15
15
 
16
16
  [workspace.dependencies]
17
- plotui-core = { version = "0.4.1", path = "crates/plotui-core" }
18
- plotui-protocol = { version = "0.4.1", path = "crates/plotui-protocol" }
19
- plotui-term = { version = "0.4.1", path = "crates/plotui-term" }
20
- plotui-bind = { version = "0.4.1", path = "crates/plotui-bind" }
21
- plotui-ratatui = { version = "0.4.1", path = "crates/plotui-ratatui" }
17
+ plotui-core = { version = "0.4.2", path = "crates/plotui-core" }
18
+ plotui-protocol = { version = "0.4.2", path = "crates/plotui-protocol" }
19
+ plotui-term = { version = "0.4.2", path = "crates/plotui-term" }
20
+ plotui-bind = { version = "0.4.2", path = "crates/plotui-bind" }
21
+ plotui-ratatui = { version = "0.4.2", path = "crates/plotui-ratatui" }
22
22
  ratatui = { version = "0.30", default-features = false }
23
23
  crossterm = "0.29"
24
24
 
@@ -1,7 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plotui
3
- Version: 0.4.1
4
- Requires-Dist: plotui-cli>=0.4.1
3
+ Version: 0.4.2
5
4
  Requires-Dist: textual>=0.60 ; extra == 'textual'
6
5
  Provides-Extra: textual
7
6
  License-File: LICENSE
@@ -80,6 +79,7 @@ printed frame when piped or with `--static`.
80
79
  ```bash
81
80
  curl -fsSL https://plotui.xyz/install.sh | sh # prebuilt binary
82
81
  brew install sebaheg/tap/plotui # Homebrew (macOS / Linux)
82
+ pip install plotui # prebuilt wheel: the library + the CLI
83
83
  cargo install plotui # build from source
84
84
  cargo binstall plotui # prebuilt, via cargo-binstall
85
85
  ```
@@ -220,12 +220,19 @@ dispatches those to every class in the MRO, so both would run).
220
220
  - [x] Graceful render-path auto-detection (placeholder / direct Kitty, with a
221
221
  supported-terminals notice elsewhere and a `PLOTUI_RENDER` override)
222
222
  - [ ] Sixel + iTerm2 OSC 1337 encoders for terminals without Kitty graphics
223
- - [ ] Prebuilt wheels (maturin + cibuildwheel)
223
+ - [x] Prebuilt wheels (maturin + GitHub Actions): `pip install plotui` — macOS
224
+ arm64/x86_64, Linux x86_64/aarch64, abi3 ≥ 3.9; the wheel bundles the
225
+ CLI binary
224
226
  - [x] Ratatui frontend (native): `plotui-ratatui` — StatefulWidget + app-owned
225
227
  PlotState, full parity with the Textual widget
226
228
  (`cargo run -p plotui-ratatui --example demo`)
227
229
  - [x] Bubble Tea frontend (cgo): `go/` bindings over the `plotui-ffi` C ABI +
228
230
  the `teaplot` component for Bubble Tea v2 (see `go/README.md`)
231
+ - [x] CLI: `plotui line|scatter|bar` from stdin or a file — interactive on a
232
+ TTY, one static frame when piped; installed via curl, Homebrew, cargo,
233
+ or pip (see Install above)
234
+ - [ ] CLI v2: `--follow` streaming, `scatter3d`, histogram/density/count
235
+ transforms, `--out png`
229
236
  - [ ] Prebuilt static libs for the Go bindings (today: local source build)
230
237
 
231
238
  ## License
@@ -67,6 +67,7 @@ printed frame when piped or with `--static`.
67
67
  ```bash
68
68
  curl -fsSL https://plotui.xyz/install.sh | sh # prebuilt binary
69
69
  brew install sebaheg/tap/plotui # Homebrew (macOS / Linux)
70
+ pip install plotui # prebuilt wheel: the library + the CLI
70
71
  cargo install plotui # build from source
71
72
  cargo binstall plotui # prebuilt, via cargo-binstall
72
73
  ```
@@ -207,12 +208,19 @@ dispatches those to every class in the MRO, so both would run).
207
208
  - [x] Graceful render-path auto-detection (placeholder / direct Kitty, with a
208
209
  supported-terminals notice elsewhere and a `PLOTUI_RENDER` override)
209
210
  - [ ] Sixel + iTerm2 OSC 1337 encoders for terminals without Kitty graphics
210
- - [ ] Prebuilt wheels (maturin + cibuildwheel)
211
+ - [x] Prebuilt wheels (maturin + GitHub Actions): `pip install plotui` — macOS
212
+ arm64/x86_64, Linux x86_64/aarch64, abi3 ≥ 3.9; the wheel bundles the
213
+ CLI binary
211
214
  - [x] Ratatui frontend (native): `plotui-ratatui` — StatefulWidget + app-owned
212
215
  PlotState, full parity with the Textual widget
213
216
  (`cargo run -p plotui-ratatui --example demo`)
214
217
  - [x] Bubble Tea frontend (cgo): `go/` bindings over the `plotui-ffi` C ABI +
215
218
  the `teaplot` component for Bubble Tea v2 (see `go/README.md`)
219
+ - [x] CLI: `plotui line|scatter|bar` from stdin or a file — interactive on a
220
+ TTY, one static frame when piped; installed via curl, Homebrew, cargo,
221
+ or pip (see Install above)
222
+ - [ ] CLI v2: `--follow` streaming, `scatter3d`, histogram/density/count
223
+ transforms, `--out png`
216
224
  - [ ] Prebuilt static libs for the Go bindings (today: local source build)
217
225
 
218
226
  ## License
@@ -67,6 +67,7 @@ printed frame when piped or with `--static`.
67
67
  ```bash
68
68
  curl -fsSL https://plotui.xyz/install.sh | sh # prebuilt binary
69
69
  brew install sebaheg/tap/plotui # Homebrew (macOS / Linux)
70
+ pip install plotui # prebuilt wheel: the library + the CLI
70
71
  cargo install plotui # build from source
71
72
  cargo binstall plotui # prebuilt, via cargo-binstall
72
73
  ```
@@ -207,12 +208,19 @@ dispatches those to every class in the MRO, so both would run).
207
208
  - [x] Graceful render-path auto-detection (placeholder / direct Kitty, with a
208
209
  supported-terminals notice elsewhere and a `PLOTUI_RENDER` override)
209
210
  - [ ] Sixel + iTerm2 OSC 1337 encoders for terminals without Kitty graphics
210
- - [ ] Prebuilt wheels (maturin + cibuildwheel)
211
+ - [x] Prebuilt wheels (maturin + GitHub Actions): `pip install plotui` — macOS
212
+ arm64/x86_64, Linux x86_64/aarch64, abi3 ≥ 3.9; the wheel bundles the
213
+ CLI binary
211
214
  - [x] Ratatui frontend (native): `plotui-ratatui` — StatefulWidget + app-owned
212
215
  PlotState, full parity with the Textual widget
213
216
  (`cargo run -p plotui-ratatui --example demo`)
214
217
  - [x] Bubble Tea frontend (cgo): `go/` bindings over the `plotui-ffi` C ABI +
215
218
  the `teaplot` component for Bubble Tea v2 (see `go/README.md`)
219
+ - [x] CLI: `plotui line|scatter|bar` from stdin or a file — interactive on a
220
+ TTY, one static frame when piped; installed via curl, Homebrew, cargo,
221
+ or pip (see Install above)
222
+ - [ ] CLI v2: `--follow` streaming, `scatter3d`, histogram/density/count
223
+ transforms, `--out png`
216
224
  - [ ] Prebuilt static libs for the Go bindings (today: local source build)
217
225
 
218
226
  ## License
@@ -67,6 +67,7 @@ printed frame when piped or with `--static`.
67
67
  ```bash
68
68
  curl -fsSL https://plotui.xyz/install.sh | sh # prebuilt binary
69
69
  brew install sebaheg/tap/plotui # Homebrew (macOS / Linux)
70
+ pip install plotui # prebuilt wheel: the library + the CLI
70
71
  cargo install plotui # build from source
71
72
  cargo binstall plotui # prebuilt, via cargo-binstall
72
73
  ```
@@ -207,12 +208,19 @@ dispatches those to every class in the MRO, so both would run).
207
208
  - [x] Graceful render-path auto-detection (placeholder / direct Kitty, with a
208
209
  supported-terminals notice elsewhere and a `PLOTUI_RENDER` override)
209
210
  - [ ] Sixel + iTerm2 OSC 1337 encoders for terminals without Kitty graphics
210
- - [ ] Prebuilt wheels (maturin + cibuildwheel)
211
+ - [x] Prebuilt wheels (maturin + GitHub Actions): `pip install plotui` — macOS
212
+ arm64/x86_64, Linux x86_64/aarch64, abi3 ≥ 3.9; the wheel bundles the
213
+ CLI binary
211
214
  - [x] Ratatui frontend (native): `plotui-ratatui` — StatefulWidget + app-owned
212
215
  PlotState, full parity with the Textual widget
213
216
  (`cargo run -p plotui-ratatui --example demo`)
214
217
  - [x] Bubble Tea frontend (cgo): `go/` bindings over the `plotui-ffi` C ABI +
215
218
  the `teaplot` component for Bubble Tea v2 (see `go/README.md`)
219
+ - [x] CLI: `plotui line|scatter|bar` from stdin or a file — interactive on a
220
+ TTY, one static frame when piped; installed via curl, Homebrew, cargo,
221
+ or pip (see Install above)
222
+ - [ ] CLI v2: `--follow` streaming, `scatter3d`, histogram/density/count
223
+ transforms, `--out png`
216
224
  - [ ] Prebuilt static libs for the Go bindings (today: local source build)
217
225
 
218
226
  ## License
@@ -67,6 +67,7 @@ printed frame when piped or with `--static`.
67
67
  ```bash
68
68
  curl -fsSL https://plotui.xyz/install.sh | sh # prebuilt binary
69
69
  brew install sebaheg/tap/plotui # Homebrew (macOS / Linux)
70
+ pip install plotui # prebuilt wheel: the library + the CLI
70
71
  cargo install plotui # build from source
71
72
  cargo binstall plotui # prebuilt, via cargo-binstall
72
73
  ```
@@ -207,12 +208,19 @@ dispatches those to every class in the MRO, so both would run).
207
208
  - [x] Graceful render-path auto-detection (placeholder / direct Kitty, with a
208
209
  supported-terminals notice elsewhere and a `PLOTUI_RENDER` override)
209
210
  - [ ] Sixel + iTerm2 OSC 1337 encoders for terminals without Kitty graphics
210
- - [ ] Prebuilt wheels (maturin + cibuildwheel)
211
+ - [x] Prebuilt wheels (maturin + GitHub Actions): `pip install plotui` — macOS
212
+ arm64/x86_64, Linux x86_64/aarch64, abi3 ≥ 3.9; the wheel bundles the
213
+ CLI binary
211
214
  - [x] Ratatui frontend (native): `plotui-ratatui` — StatefulWidget + app-owned
212
215
  PlotState, full parity with the Textual widget
213
216
  (`cargo run -p plotui-ratatui --example demo`)
214
217
  - [x] Bubble Tea frontend (cgo): `go/` bindings over the `plotui-ffi` C ABI +
215
218
  the `teaplot` component for Bubble Tea v2 (see `go/README.md`)
219
+ - [x] CLI: `plotui line|scatter|bar` from stdin or a file — interactive on a
220
+ TTY, one static frame when piped; installed via curl, Homebrew, cargo,
221
+ or pip (see Install above)
222
+ - [ ] CLI v2: `--follow` streaming, `scatter3d`, histogram/density/count
223
+ transforms, `--out png`
216
224
  - [ ] Prebuilt static libs for the Go bindings (today: local source build)
217
225
 
218
226
  ## License
@@ -0,0 +1,228 @@
1
+ # plotui
2
+
3
+ **Interactive 2D/3D plots in the terminal — Plotly-style — for Textual, Ratatui, and Bubble Tea, powered by a Rust core and the Kitty graphics protocol.**
4
+
5
+ `plotui` renders scatter plots (and, soon, lines / surfaces / bars) as real
6
+ pixel graphics inside a terminal, and lets you rotate, pan, and zoom them. It
7
+ drops into a [Textual](https://textual.textualize.io/),
8
+ [Ratatui](https://ratatui.rs/), or
9
+ [Bubble Tea](https://github.com/charmbracelet/bubbletea) app as a first-class
10
+ widget, with the rendering engine written in Rust so it stays fast in 2D and 3D.
11
+
12
+ > Status: **early scaffold.** Working today: 2D scatter/line/bar charts with
13
+ > axes, ticks, and a legend; a 3D scatter/graph engine; a Kitty-image raw demo;
14
+ > and a Textual widget. See the roadmap below.
15
+
16
+ ## Architecture
17
+
18
+ The one rule that shapes everything: **the Rust core owns pixels, not the
19
+ terminal.** It has no event loop and no input handling — the TUI framework
20
+ (Textual, Ratatui, or Bubble Tea) owns the loop, forwards input to the
21
+ camera, and asks for a frame.
22
+
23
+ ```
24
+ crates/
25
+ plotui-core/ pure engine: data model, 3D camera, rasterizer → RGBA
26
+ plotui-protocol/ RGBA → terminal bytes (Kitty graphics protocol)
27
+ plotui-term/ shared frontend glue: render-path detection, cell-pixel
28
+ probing, tmux passthrough, the per-frame render policy
29
+ plotui-bind/ shared binding semantics: parsing, validation, defaults,
30
+ and their exact error messages (Python and Go agree)
31
+ plotui-py/ PyO3 bindings → the `plotui._plotui` native module
32
+ plotui-ratatui/ Ratatui widget (native Rust frontend)
33
+ plotui-ffi/ C ABI (cdylib + staticlib) behind the Go bindings
34
+ python/plotui/ the Python package + Textual `PlotWidget`
35
+ go/ Go bindings + `teaplot`, the Bubble Tea v2 component
36
+ examples/ raw_demo.py (Kitty images), textual_demo.py
37
+ ```
38
+
39
+ `core` and `protocol` are pure and I/O-free, so the same engine can back every
40
+ frontend and be unit-tested by hashing pixel buffers.
41
+
42
+ ## Integrations
43
+
44
+ Each TUI framework gets a first-class widget, not a port. All frontends sit on
45
+ the same policy crates (`plotui-term` for detection/tmux/render policy,
46
+ `plotui-bind` for argument validation and its exact error strings), so a plot
47
+ looks and behaves identically whichever framework hosts it — down to the error
48
+ messages.
49
+
50
+ | Frontend | How it works | Where in the codebase | Try it |
51
+ | --- | --- | --- | --- |
52
+ | **Textual** (Python) | `PlotWidget` wraps the `plotui._plotui` native module (PyO3). Mouse events route to the camera, hover/click picking arrives as Textual messages, `extend` streams points in-place, and text overlays splice into the image without re-rasterizing. | `python/plotui/textual.py`; native module in `crates/plotui-py` | `python examples/textual_graph.py` |
53
+ | **Ratatui** (Rust) | A native `StatefulWidget` plus an app-owned `PlotState`: hand it crossterm events, draw it like any other widget — frames and Kitty placement ride ratatui's own buffer diff, flicker-free. | `crates/plotui-ratatui` | `cargo run -p plotui-ratatui --example demo` |
54
+ | **Bubble Tea** (Go) | `teaplot.New(plot)` returns an Elm-style model: `Update` consumes tea mouse/key events, `View` lays out the cell grid, and image escapes leave as `tea.Raw` commands. Links to the Rust engine statically over the `plotui-ffi` C ABI (cgo). | `go/` (bindings) + `go/teaplot` (component); ABI in `crates/plotui-ffi` | `go run ./examples/demo` from `go/` — see [go/README.md](go/README.md) |
55
+ | **Browser** (WASM) | The same engine compiled to WebAssembly drives the live demos on the website: pointer events feed the engine's own camera, and every frame is its RGBA bytes blitted onto a canvas. Not a plotting-in-the-browser product — it exists so the site can show the real renderer. | `crates/plotui-wasm`; consumed by `site/` | [plotui.xyz/examples.html](https://plotui.xyz/examples.html) |
56
+
57
+ The three TUI widgets have feature parity: render-path detection, tmux
58
+ passthrough, drag/zoom/pan/keys, picking + hover, the 2D crosshair, text
59
+ overlays, half-resolution interaction frames, and streaming extend.
60
+
61
+ ## Install the CLI
62
+
63
+ `plotui` is also a command-line tool: pipe columns of numbers in, get a
64
+ real-pixel chart out — interactive on a TTY (pan, zoom, crosshair), a single
65
+ printed frame when piped or with `--static`.
66
+
67
+ ```bash
68
+ curl -fsSL https://plotui.xyz/install.sh | sh # prebuilt binary
69
+ brew install sebaheg/tap/plotui # Homebrew (macOS / Linux)
70
+ pip install plotui # prebuilt wheel: the library + the CLI
71
+ cargo install plotui # build from source
72
+ cargo binstall plotui # prebuilt, via cargo-binstall
73
+ ```
74
+
75
+ ```bash
76
+ seq 1 100 | awk '{print $1, sin($1/10)}' | plotui line
77
+ plotui scatter -H -d, data.csv # header row + comma-delimited
78
+ plotui bar counts.tsv
79
+ ```
80
+
81
+ Like every plotui frontend, the CLI needs a terminal with Kitty graphics
82
+ (supported terminals below); elsewhere it prints a notice and exits.
83
+
84
+ ## Develop
85
+
86
+ Requires Rust and Python 3.9+. Build the native module into a virtualenv with
87
+ [maturin](https://www.maturin.rs/):
88
+
89
+ ```bash
90
+ python -m venv .venv && source .venv/bin/activate
91
+ pip install maturin textual
92
+ maturin develop --release
93
+ ```
94
+
95
+ Then, in a terminal with Kitty graphics support — **Kitty**, **Ghostty**,
96
+ **iTerm2 ≥ 3.5**, **WezTerm**, or **Konsole** — for the full-resolution pixel demos:
97
+
98
+ ```bash
99
+ python examples/raw_demo.py # 3D scatter via Kitty images
100
+ python examples/textual_demo.py # embedded in Textual
101
+ python examples/textual_graph.py # interactive graph: hover + click-to-inspect
102
+ ```
103
+
104
+ The Textual widget picks its render path per terminal: Unicode-placeholder
105
+ Kitty graphics in Kitty/Ghostty, direct Kitty placement in iTerm2/WezTerm/
106
+ Konsole — plus Warp, Rio, and VS Code, whose younger Kitty decoders are
107
+ supported but still maturing (VS Code needs its
108
+ `terminal.integrated.enableImages` setting). plotui only draws
109
+ real pixels — terminals without Kitty graphics get a notice naming supported
110
+ terminals, never a degraded plot. Override with
111
+ `PLOTUI_RENDER=placeholder|direct` or `PlotWidget(..., render_mode=...)`.
112
+
113
+ ## Python API
114
+
115
+ ```python
116
+ from plotui import Plot
117
+
118
+ # 2D: axes, ticks, and a legend appear automatically. Traces added without a
119
+ # color take palette slots in fixed order; `name=` puts a series in the legend.
120
+ plot = Plot()
121
+ plot.add_line(xs, ys, name="forecast")
122
+ plot.add_scatter(xs2, ys2, name="observed")
123
+ plot.add_bar(xs3, heights)
124
+
125
+ # Secondary axes: axis="y2"/"y3" bind a series to an independent right-hand
126
+ # axis — its own autoscale and tick column, labels tinted to the series color
127
+ # (y2 innermost, y3 outermost). The grid stays with the left axis.
128
+ plot.add_line(xs, tokens, name="tokens", axis="y2")
129
+ plot.add_line(xs, cpu_minutes, name="cpu min", axis="y3")
130
+
131
+ # 3D: any 3D trace switches the plot to the orbit camera.
132
+ plot = Plot()
133
+ plot.add_scatter3d(xs, ys, zs, color=(230, 60, 120), size=2.0)
134
+
135
+ # Streaming: every add_* returns a trace handle. Append through it instead
136
+ # of rebuilding — O(new points), autoscale follows; numpy arrays are read
137
+ # in one bulk copy. set_visible toggles a series without losing its handle,
138
+ # palette slot, or node indices.
139
+ h = plot.add_line([], [], name="loss")
140
+ plot.extend(h, xs, ys) # 3D scatter/line: extend(h, xs, ys, zs)
141
+ plot.set_visible(h, False)
142
+
143
+ # Interaction (forward your framework's events to these):
144
+ plot.rotate(d_yaw, d_pitch)
145
+ plot.zoom_by(factor)
146
+ plot.pan(dx, dy)
147
+ plot.reset()
148
+
149
+ # Render (the frontend places the bytes):
150
+ escape = plot.render_kitty(cols, rows, cell_w, cell_h) # Kitty pixel image
151
+ pixels = plot.render_rgba(px_w, px_h) # raw RGBA8 bytes
152
+ ```
153
+
154
+ Graphs take per-element styling, and the camera/projection state is fully
155
+ scriptable — the hooks a host needs for label overlays, camera targeting, and
156
+ rebuilding a plot without losing the view:
157
+
158
+ ```python
159
+ plot.add_graph3d(xs, ys, zs, edges=[(0, 1), (1, 2)],
160
+ node_colors=[...], # one (r, g, b) per node
161
+ node_sizes=[...], # per-node radius (else `size`)
162
+ edge_colors=[...], # per-edge (r, g, b) (else derived)
163
+ node_shapes=[...]) # per-node "disc" | "ring" | "square" |
164
+ # "triangle" | "diamond" | "diamond-open" | "dot"
165
+ plot.set_show_box(False) # hide the 3D orientation cube
166
+ plot.set_bounds((x0, y0, z0), (x1, y1, z1)) # pin the data frame (else the nodes'
167
+ # bounding box); None, None restores
168
+ plot.set_chrome(grid=(26, 32, 36), # recolour the non-data chrome to sit on
169
+ frame=(43, 50, 55), # your own background: bg (legend box),
170
+ ink=(103, 111, 118)) # frame, grid, ink, ink_bright
171
+
172
+ state = plot.camera_state() # (yaw, pitch, zoom, pan_x, pan_y)
173
+ plot.set_camera_state(*state) # restore (e.g. onto a new Plot)
174
+ plot.project_nodes(px_w, px_h) # [(x_px, y_px, depth)] per node —
175
+ # exact render/pick geometry
176
+ ```
177
+
178
+ In Textual, use `plotui.textual.PlotWidget(plot)` and it handles the event
179
+ plumbing for you. Pass `pickable=True` to make 3D graph nodes and edges
180
+ interactive: hovering lights the element under the cursor up white, and
181
+ clicking posts an `ElementPicked` message with `("node", i)` or `("edge", i)`
182
+ (see `examples/textual_graph.py`, which opens a slide-in inspector from it).
183
+
184
+ The widget also supports **text overlays** — `widget.set_overlay([(row, col,
185
+ text, style), ...])` splices terminal-crisp text (labels, badges) over the
186
+ image in every render mode without re-rasterizing — and exposes a
187
+ `widget.dragging` property for hosts that defer work mid-gesture. To customize
188
+ interaction in a subclass, override the `apply_rotate` / `apply_pan` /
189
+ `apply_zoom` / `apply_reset` / `on_click_at` primitives that every input path
190
+ routes through — do **not** override the Textual `on_*` handlers (Textual
191
+ dispatches those to every class in the MRO, so both would run).
192
+
193
+ ## Roadmap
194
+
195
+ - [x] Flicker-free Kitty placement via Unicode-placeholder virtual placement
196
+ (fixed image id, atomic replace) — wire the pixel path into the Textual widget
197
+ - [x] 2D traces: scatter, line, bar; axes, ticks, tick labels, legend
198
+ - [x] Independent right-hand y-axes (`axis="y2"`/`"y3"`) with tinted tick labels
199
+ - [ ] 2D step trace; axis titles; time-formatted x ticks
200
+ - [ ] 3D surface / mesh; axis cube with labels
201
+ - [x] Interactive hover / pick for 3D graph nodes *and* edges (opt-in via
202
+ `PlotWidget(..., pickable=True)`: hover lights the element up white,
203
+ click posts `ElementPicked`)
204
+ - [ ] Hover / pick for 2D traces; spatial index for large graphs
205
+ - [x] Streaming append: trace handles, `extend`, `set_visible`, incremental bounds
206
+ - [x] numpy fast-path input (one bulk copy, no per-element conversion)
207
+ - [ ] Rolling window (`max_points`) for endless streams
208
+ - [x] Graceful render-path auto-detection (placeholder / direct Kitty, with a
209
+ supported-terminals notice elsewhere and a `PLOTUI_RENDER` override)
210
+ - [ ] Sixel + iTerm2 OSC 1337 encoders for terminals without Kitty graphics
211
+ - [x] Prebuilt wheels (maturin + GitHub Actions): `pip install plotui` — macOS
212
+ arm64/x86_64, Linux x86_64/aarch64, abi3 ≥ 3.9; the wheel bundles the
213
+ CLI binary
214
+ - [x] Ratatui frontend (native): `plotui-ratatui` — StatefulWidget + app-owned
215
+ PlotState, full parity with the Textual widget
216
+ (`cargo run -p plotui-ratatui --example demo`)
217
+ - [x] Bubble Tea frontend (cgo): `go/` bindings over the `plotui-ffi` C ABI +
218
+ the `teaplot` component for Bubble Tea v2 (see `go/README.md`)
219
+ - [x] CLI: `plotui line|scatter|bar` from stdin or a file — interactive on a
220
+ TTY, one static frame when piped; installed via curl, Homebrew, cargo,
221
+ or pip (see Install above)
222
+ - [ ] CLI v2: `--follow` streaming, `scatter3d`, histogram/density/count
223
+ transforms, `--out png`
224
+ - [ ] Prebuilt static libs for the Go bindings (today: local source build)
225
+
226
+ ## License
227
+
228
+ MIT
@@ -4,23 +4,31 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "plotui"
7
- version = "0.4.1"
7
+ version = "0.4.2"
8
8
  description = "Interactive 2D/3D plots in the terminal — Plotly-style, for Textual, powered by a Rust core and the Kitty graphics protocol."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
11
11
  license = { text = "MIT" }
12
12
  keywords = ["terminal", "plot", "3d", "textual", "kitty", "tui", "visualization"]
13
- # plotui-cli is the compiled `plotui` binary in a wheel (built from
14
- # crates/plotui-cli); depending on it puts the CLI on PATH for pip users.
15
- dependencies = ["plotui-cli>=0.4.1"]
13
+ dependencies = []
16
14
 
17
15
  [project.optional-dependencies]
18
16
  textual = ["textual>=0.60"]
19
17
 
18
+ # The CLI ships inside the wheel: CI compiles crates/plotui-cli for the wheel's
19
+ # target into python/plotui/_bin/plotui, and this entry point execs it.
20
+ [project.scripts]
21
+ plotui = "plotui._cli:main"
22
+
20
23
  [tool.maturin]
21
24
  # maturin declares License-File: LICENSE in the metadata but does not put the
22
- # file in the sdist on its own, and PyPI rejects that mismatch
23
- include = [{ path = "LICENSE", format = "sdist" }]
25
+ # file in the sdist on its own, and PyPI rejects that mismatch. The _bin glob
26
+ # bundles the prebuilt CLI when present (wheel builds); it matches nothing in
27
+ # sdists and source builds.
28
+ include = [
29
+ { path = "LICENSE", format = "sdist" },
30
+ { path = "python/plotui/_bin/*", format = "wheel" },
31
+ ]
24
32
  manifest-path = "crates/plotui-py/Cargo.toml"
25
33
  module-name = "plotui._plotui"
26
34
  python-source = "python"
@@ -0,0 +1,25 @@
1
+ """The `plotui` console script: exec the bundled native CLI binary.
2
+
3
+ Prebuilt wheels carry the compiled CLI at ``plotui/_bin/plotui``; this
4
+ entry point replaces the Python process with it, so ``plotui`` on the
5
+ command line *is* the native binary. Source builds don't bundle it.
6
+ """
7
+
8
+ import os
9
+ import sys
10
+
11
+
12
+ def main() -> None:
13
+ exe = os.path.join(os.path.dirname(__file__), "_bin", "plotui")
14
+ if not os.path.exists(exe):
15
+ sys.stderr.write(
16
+ "plotui: this install does not bundle the CLI (source builds don't).\n"
17
+ "Use a prebuilt wheel, or another install method: https://plotui.xyz/#install\n"
18
+ )
19
+ raise SystemExit(1)
20
+ argv = [exe, *sys.argv[1:]]
21
+ try:
22
+ os.execv(exe, argv)
23
+ except PermissionError:
24
+ os.chmod(exe, 0o755)
25
+ os.execv(exe, argv)
@@ -520,7 +520,9 @@ class PlotWidget(Widget, can_focus=True):
520
520
  # pointer instead of lagging it.
521
521
  self.apply_pan(dx * self._cell_w, dy * self._cell_h)
522
522
  else:
523
- self.apply_rotate(dx * 0.03, dy * 0.03)
523
+ # Negated: dragging grabs the camera, not the object — drag
524
+ # right orbits the view right (matches the website examples).
525
+ self.apply_rotate(-dx * 0.03, -dy * 0.03)
524
526
  elif not self._plot.is_3d():
525
527
  if self._crosshair:
526
528
  px_w, px_h, px, py, _ = self._pixel_geometry(event.x, event.y)
@@ -566,13 +568,13 @@ class PlotWidget(Widget, can_focus=True):
566
568
  elif key in ("minus", "-"):
567
569
  self.apply_zoom(0.9)
568
570
  elif key == "left":
569
- self.apply_rotate(-0.1, 0.0)
570
- elif key == "right":
571
571
  self.apply_rotate(0.1, 0.0)
572
+ elif key == "right":
573
+ self.apply_rotate(-0.1, 0.0)
572
574
  elif key == "up":
573
- self.apply_rotate(0.0, -0.1)
574
- elif key == "down":
575
575
  self.apply_rotate(0.0, 0.1)
576
+ elif key == "down":
577
+ self.apply_rotate(0.0, -0.1)
576
578
  elif key == "shift+left":
577
579
  self.apply_pan(-2.0 * self._cell_w, 0.0)
578
580
  elif key == "shift+right":
@@ -1,220 +0,0 @@
1
- # plotui
2
-
3
- **Interactive 2D/3D plots in the terminal — Plotly-style — for Textual, Ratatui, and Bubble Tea, powered by a Rust core and the Kitty graphics protocol.**
4
-
5
- `plotui` renders scatter plots (and, soon, lines / surfaces / bars) as real
6
- pixel graphics inside a terminal, and lets you rotate, pan, and zoom them. It
7
- drops into a [Textual](https://textual.textualize.io/),
8
- [Ratatui](https://ratatui.rs/), or
9
- [Bubble Tea](https://github.com/charmbracelet/bubbletea) app as a first-class
10
- widget, with the rendering engine written in Rust so it stays fast in 2D and 3D.
11
-
12
- > Status: **early scaffold.** Working today: 2D scatter/line/bar charts with
13
- > axes, ticks, and a legend; a 3D scatter/graph engine; a Kitty-image raw demo;
14
- > and a Textual widget. See the roadmap below.
15
-
16
- ## Architecture
17
-
18
- The one rule that shapes everything: **the Rust core owns pixels, not the
19
- terminal.** It has no event loop and no input handling — the TUI framework
20
- (Textual, Ratatui, or Bubble Tea) owns the loop, forwards input to the
21
- camera, and asks for a frame.
22
-
23
- ```
24
- crates/
25
- plotui-core/ pure engine: data model, 3D camera, rasterizer → RGBA
26
- plotui-protocol/ RGBA → terminal bytes (Kitty graphics protocol)
27
- plotui-term/ shared frontend glue: render-path detection, cell-pixel
28
- probing, tmux passthrough, the per-frame render policy
29
- plotui-bind/ shared binding semantics: parsing, validation, defaults,
30
- and their exact error messages (Python and Go agree)
31
- plotui-py/ PyO3 bindings → the `plotui._plotui` native module
32
- plotui-ratatui/ Ratatui widget (native Rust frontend)
33
- plotui-ffi/ C ABI (cdylib + staticlib) behind the Go bindings
34
- python/plotui/ the Python package + Textual `PlotWidget`
35
- go/ Go bindings + `teaplot`, the Bubble Tea v2 component
36
- examples/ raw_demo.py (Kitty images), textual_demo.py
37
- ```
38
-
39
- `core` and `protocol` are pure and I/O-free, so the same engine can back every
40
- frontend and be unit-tested by hashing pixel buffers.
41
-
42
- ## Integrations
43
-
44
- Each TUI framework gets a first-class widget, not a port. All frontends sit on
45
- the same policy crates (`plotui-term` for detection/tmux/render policy,
46
- `plotui-bind` for argument validation and its exact error strings), so a plot
47
- looks and behaves identically whichever framework hosts it — down to the error
48
- messages.
49
-
50
- | Frontend | How it works | Where in the codebase | Try it |
51
- | --- | --- | --- | --- |
52
- | **Textual** (Python) | `PlotWidget` wraps the `plotui._plotui` native module (PyO3). Mouse events route to the camera, hover/click picking arrives as Textual messages, `extend` streams points in-place, and text overlays splice into the image without re-rasterizing. | `python/plotui/textual.py`; native module in `crates/plotui-py` | `python examples/textual_graph.py` |
53
- | **Ratatui** (Rust) | A native `StatefulWidget` plus an app-owned `PlotState`: hand it crossterm events, draw it like any other widget — frames and Kitty placement ride ratatui's own buffer diff, flicker-free. | `crates/plotui-ratatui` | `cargo run -p plotui-ratatui --example demo` |
54
- | **Bubble Tea** (Go) | `teaplot.New(plot)` returns an Elm-style model: `Update` consumes tea mouse/key events, `View` lays out the cell grid, and image escapes leave as `tea.Raw` commands. Links to the Rust engine statically over the `plotui-ffi` C ABI (cgo). | `go/` (bindings) + `go/teaplot` (component); ABI in `crates/plotui-ffi` | `go run ./examples/demo` from `go/` — see [go/README.md](go/README.md) |
55
- | **Browser** (WASM) | The same engine compiled to WebAssembly drives the live demos on the website: pointer events feed the engine's own camera, and every frame is its RGBA bytes blitted onto a canvas. Not a plotting-in-the-browser product — it exists so the site can show the real renderer. | `crates/plotui-wasm`; consumed by `site/` | [plotui.xyz/examples.html](https://plotui.xyz/examples.html) |
56
-
57
- The three TUI widgets have feature parity: render-path detection, tmux
58
- passthrough, drag/zoom/pan/keys, picking + hover, the 2D crosshair, text
59
- overlays, half-resolution interaction frames, and streaming extend.
60
-
61
- ## Install the CLI
62
-
63
- `plotui` is also a command-line tool: pipe columns of numbers in, get a
64
- real-pixel chart out — interactive on a TTY (pan, zoom, crosshair), a single
65
- printed frame when piped or with `--static`.
66
-
67
- ```bash
68
- curl -fsSL https://plotui.xyz/install.sh | sh # prebuilt binary
69
- brew install sebaheg/tap/plotui # Homebrew (macOS / Linux)
70
- cargo install plotui # build from source
71
- cargo binstall plotui # prebuilt, via cargo-binstall
72
- ```
73
-
74
- ```bash
75
- seq 1 100 | awk '{print $1, sin($1/10)}' | plotui line
76
- plotui scatter -H -d, data.csv # header row + comma-delimited
77
- plotui bar counts.tsv
78
- ```
79
-
80
- Like every plotui frontend, the CLI needs a terminal with Kitty graphics
81
- (supported terminals below); elsewhere it prints a notice and exits.
82
-
83
- ## Develop
84
-
85
- Requires Rust and Python 3.9+. Build the native module into a virtualenv with
86
- [maturin](https://www.maturin.rs/):
87
-
88
- ```bash
89
- python -m venv .venv && source .venv/bin/activate
90
- pip install maturin textual
91
- maturin develop --release
92
- ```
93
-
94
- Then, in a terminal with Kitty graphics support — **Kitty**, **Ghostty**,
95
- **iTerm2 ≥ 3.5**, **WezTerm**, or **Konsole** — for the full-resolution pixel demos:
96
-
97
- ```bash
98
- python examples/raw_demo.py # 3D scatter via Kitty images
99
- python examples/textual_demo.py # embedded in Textual
100
- python examples/textual_graph.py # interactive graph: hover + click-to-inspect
101
- ```
102
-
103
- The Textual widget picks its render path per terminal: Unicode-placeholder
104
- Kitty graphics in Kitty/Ghostty, direct Kitty placement in iTerm2/WezTerm/
105
- Konsole — plus Warp, Rio, and VS Code, whose younger Kitty decoders are
106
- supported but still maturing (VS Code needs its
107
- `terminal.integrated.enableImages` setting). plotui only draws
108
- real pixels — terminals without Kitty graphics get a notice naming supported
109
- terminals, never a degraded plot. Override with
110
- `PLOTUI_RENDER=placeholder|direct` or `PlotWidget(..., render_mode=...)`.
111
-
112
- ## Python API
113
-
114
- ```python
115
- from plotui import Plot
116
-
117
- # 2D: axes, ticks, and a legend appear automatically. Traces added without a
118
- # color take palette slots in fixed order; `name=` puts a series in the legend.
119
- plot = Plot()
120
- plot.add_line(xs, ys, name="forecast")
121
- plot.add_scatter(xs2, ys2, name="observed")
122
- plot.add_bar(xs3, heights)
123
-
124
- # Secondary axes: axis="y2"/"y3" bind a series to an independent right-hand
125
- # axis — its own autoscale and tick column, labels tinted to the series color
126
- # (y2 innermost, y3 outermost). The grid stays with the left axis.
127
- plot.add_line(xs, tokens, name="tokens", axis="y2")
128
- plot.add_line(xs, cpu_minutes, name="cpu min", axis="y3")
129
-
130
- # 3D: any 3D trace switches the plot to the orbit camera.
131
- plot = Plot()
132
- plot.add_scatter3d(xs, ys, zs, color=(230, 60, 120), size=2.0)
133
-
134
- # Streaming: every add_* returns a trace handle. Append through it instead
135
- # of rebuilding — O(new points), autoscale follows; numpy arrays are read
136
- # in one bulk copy. set_visible toggles a series without losing its handle,
137
- # palette slot, or node indices.
138
- h = plot.add_line([], [], name="loss")
139
- plot.extend(h, xs, ys) # 3D scatter/line: extend(h, xs, ys, zs)
140
- plot.set_visible(h, False)
141
-
142
- # Interaction (forward your framework's events to these):
143
- plot.rotate(d_yaw, d_pitch)
144
- plot.zoom_by(factor)
145
- plot.pan(dx, dy)
146
- plot.reset()
147
-
148
- # Render (the frontend places the bytes):
149
- escape = plot.render_kitty(cols, rows, cell_w, cell_h) # Kitty pixel image
150
- pixels = plot.render_rgba(px_w, px_h) # raw RGBA8 bytes
151
- ```
152
-
153
- Graphs take per-element styling, and the camera/projection state is fully
154
- scriptable — the hooks a host needs for label overlays, camera targeting, and
155
- rebuilding a plot without losing the view:
156
-
157
- ```python
158
- plot.add_graph3d(xs, ys, zs, edges=[(0, 1), (1, 2)],
159
- node_colors=[...], # one (r, g, b) per node
160
- node_sizes=[...], # per-node radius (else `size`)
161
- edge_colors=[...], # per-edge (r, g, b) (else derived)
162
- node_shapes=[...]) # per-node "disc" | "ring" | "square" |
163
- # "triangle" | "diamond" | "diamond-open" | "dot"
164
- plot.set_show_box(False) # hide the 3D orientation cube
165
- plot.set_bounds((x0, y0, z0), (x1, y1, z1)) # pin the data frame (else the nodes'
166
- # bounding box); None, None restores
167
- plot.set_chrome(grid=(26, 32, 36), # recolour the non-data chrome to sit on
168
- frame=(43, 50, 55), # your own background: bg (legend box),
169
- ink=(103, 111, 118)) # frame, grid, ink, ink_bright
170
-
171
- state = plot.camera_state() # (yaw, pitch, zoom, pan_x, pan_y)
172
- plot.set_camera_state(*state) # restore (e.g. onto a new Plot)
173
- plot.project_nodes(px_w, px_h) # [(x_px, y_px, depth)] per node —
174
- # exact render/pick geometry
175
- ```
176
-
177
- In Textual, use `plotui.textual.PlotWidget(plot)` and it handles the event
178
- plumbing for you. Pass `pickable=True` to make 3D graph nodes and edges
179
- interactive: hovering lights the element under the cursor up white, and
180
- clicking posts an `ElementPicked` message with `("node", i)` or `("edge", i)`
181
- (see `examples/textual_graph.py`, which opens a slide-in inspector from it).
182
-
183
- The widget also supports **text overlays** — `widget.set_overlay([(row, col,
184
- text, style), ...])` splices terminal-crisp text (labels, badges) over the
185
- image in every render mode without re-rasterizing — and exposes a
186
- `widget.dragging` property for hosts that defer work mid-gesture. To customize
187
- interaction in a subclass, override the `apply_rotate` / `apply_pan` /
188
- `apply_zoom` / `apply_reset` / `on_click_at` primitives that every input path
189
- routes through — do **not** override the Textual `on_*` handlers (Textual
190
- dispatches those to every class in the MRO, so both would run).
191
-
192
- ## Roadmap
193
-
194
- - [x] Flicker-free Kitty placement via Unicode-placeholder virtual placement
195
- (fixed image id, atomic replace) — wire the pixel path into the Textual widget
196
- - [x] 2D traces: scatter, line, bar; axes, ticks, tick labels, legend
197
- - [x] Independent right-hand y-axes (`axis="y2"`/`"y3"`) with tinted tick labels
198
- - [ ] 2D step trace; axis titles; time-formatted x ticks
199
- - [ ] 3D surface / mesh; axis cube with labels
200
- - [x] Interactive hover / pick for 3D graph nodes *and* edges (opt-in via
201
- `PlotWidget(..., pickable=True)`: hover lights the element up white,
202
- click posts `ElementPicked`)
203
- - [ ] Hover / pick for 2D traces; spatial index for large graphs
204
- - [x] Streaming append: trace handles, `extend`, `set_visible`, incremental bounds
205
- - [x] numpy fast-path input (one bulk copy, no per-element conversion)
206
- - [ ] Rolling window (`max_points`) for endless streams
207
- - [x] Graceful render-path auto-detection (placeholder / direct Kitty, with a
208
- supported-terminals notice elsewhere and a `PLOTUI_RENDER` override)
209
- - [ ] Sixel + iTerm2 OSC 1337 encoders for terminals without Kitty graphics
210
- - [ ] Prebuilt wheels (maturin + cibuildwheel)
211
- - [x] Ratatui frontend (native): `plotui-ratatui` — StatefulWidget + app-owned
212
- PlotState, full parity with the Textual widget
213
- (`cargo run -p plotui-ratatui --example demo`)
214
- - [x] Bubble Tea frontend (cgo): `go/` bindings over the `plotui-ffi` C ABI +
215
- the `teaplot` component for Bubble Tea v2 (see `go/README.md`)
216
- - [ ] Prebuilt static libs for the Go bindings (today: local source build)
217
-
218
- ## License
219
-
220
- MIT
File without changes
File without changes