plotui 0.4.2__tar.gz → 0.5.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.
- {plotui-0.4.2 → plotui-0.5.0}/Cargo.lock +11 -9
- {plotui-0.4.2 → plotui-0.5.0}/Cargo.toml +6 -6
- {plotui-0.4.2 → plotui-0.5.0}/PKG-INFO +90 -43
- {plotui-0.4.2 → plotui-0.5.0}/README.md +89 -42
- {plotui-0.4.2 → plotui-0.5.0}/crates/plotui-bind/README.md +89 -42
- plotui-0.5.0/crates/plotui-bind/src/dot.rs +886 -0
- plotui-0.5.0/crates/plotui-bind/src/lib.rs +1024 -0
- {plotui-0.4.2 → plotui-0.5.0}/crates/plotui-core/README.md +89 -42
- plotui-0.5.0/crates/plotui-core/fonts/MartianMono-OFL.txt +93 -0
- plotui-0.5.0/crates/plotui-core/src/font.rs +635 -0
- plotui-0.5.0/crates/plotui-core/src/glyphs.rs +2303 -0
- plotui-0.5.0/crates/plotui-core/src/layout.rs +1082 -0
- plotui-0.5.0/crates/plotui-core/src/lib.rs +8679 -0
- plotui-0.5.0/crates/plotui-core/src/marching.rs +567 -0
- plotui-0.5.0/crates/plotui-core/src/ribbon.rs +450 -0
- plotui-0.5.0/crates/plotui-core/src/ticks.rs +465 -0
- plotui-0.5.0/crates/plotui-core/tests/input_map.rs +166 -0
- plotui-0.5.0/crates/plotui-core/tests/pick_surface.rs +105 -0
- plotui-0.5.0/crates/plotui-core/tests/render.rs +1831 -0
- plotui-0.5.0/crates/plotui-protocol/README.md +275 -0
- plotui-0.5.0/crates/plotui-py/src/lib.rs +1588 -0
- plotui-0.5.0/crates/plotui-term/README.md +275 -0
- {plotui-0.4.2 → plotui-0.5.0}/crates/plotui-term/src/policy.rs +11 -0
- {plotui-0.4.2 → plotui-0.5.0}/pyproject.toml +1 -1
- plotui-0.5.0/python/plotui/__init__.py +47 -0
- {plotui-0.4.2 → plotui-0.5.0}/python/plotui/textual.py +194 -12
- plotui-0.4.2/crates/plotui-bind/src/lib.rs +0 -304
- plotui-0.4.2/crates/plotui-core/src/font.rs +0 -279
- plotui-0.4.2/crates/plotui-core/src/hershey.rs +0 -267
- plotui-0.4.2/crates/plotui-core/src/lib.rs +0 -2798
- plotui-0.4.2/crates/plotui-core/src/ticks.rs +0 -97
- plotui-0.4.2/crates/plotui-core/tests/render.rs +0 -860
- plotui-0.4.2/crates/plotui-protocol/README.md +0 -228
- plotui-0.4.2/crates/plotui-py/src/lib.rs +0 -612
- plotui-0.4.2/crates/plotui-term/README.md +0 -228
- plotui-0.4.2/python/plotui/__init__.py +0 -24
- {plotui-0.4.2 → plotui-0.5.0}/LICENSE +0 -0
- {plotui-0.4.2 → plotui-0.5.0}/crates/plotui-bind/Cargo.toml +0 -0
- {plotui-0.4.2 → plotui-0.5.0}/crates/plotui-core/Cargo.toml +0 -0
- {plotui-0.4.2 → plotui-0.5.0}/crates/plotui-protocol/Cargo.toml +0 -0
- {plotui-0.4.2 → plotui-0.5.0}/crates/plotui-protocol/src/diacritics.rs +0 -0
- {plotui-0.4.2 → plotui-0.5.0}/crates/plotui-protocol/src/lib.rs +0 -0
- {plotui-0.4.2 → plotui-0.5.0}/crates/plotui-protocol/tests/protocol.rs +0 -0
- {plotui-0.4.2 → plotui-0.5.0}/crates/plotui-py/Cargo.toml +0 -0
- {plotui-0.4.2 → plotui-0.5.0}/crates/plotui-term/Cargo.toml +0 -0
- {plotui-0.4.2 → plotui-0.5.0}/crates/plotui-term/src/compose.rs +0 -0
- {plotui-0.4.2 → plotui-0.5.0}/crates/plotui-term/src/detect.rs +0 -0
- {plotui-0.4.2 → plotui-0.5.0}/crates/plotui-term/src/ids.rs +0 -0
- {plotui-0.4.2 → plotui-0.5.0}/crates/plotui-term/src/lib.rs +0 -0
- {plotui-0.4.2 → plotui-0.5.0}/crates/plotui-term/src/tmux.rs +0 -0
- {plotui-0.4.2 → plotui-0.5.0}/crates/plotui-term/tests/term.rs +0 -0
- {plotui-0.4.2 → plotui-0.5.0}/python/plotui/_cli.py +0 -0
|
@@ -305,6 +305,7 @@ dependencies = [
|
|
|
305
305
|
"crossterm_winapi",
|
|
306
306
|
"derive_more",
|
|
307
307
|
"document-features",
|
|
308
|
+
"filedescriptor",
|
|
308
309
|
"mio",
|
|
309
310
|
"parking_lot",
|
|
310
311
|
"rustix",
|
|
@@ -1117,10 +1118,11 @@ checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
|
|
1117
1118
|
|
|
1118
1119
|
[[package]]
|
|
1119
1120
|
name = "plotui"
|
|
1120
|
-
version = "0.
|
|
1121
|
+
version = "0.5.0"
|
|
1121
1122
|
dependencies = [
|
|
1122
1123
|
"clap",
|
|
1123
1124
|
"crossterm",
|
|
1125
|
+
"plotui-bind",
|
|
1124
1126
|
"plotui-core",
|
|
1125
1127
|
"plotui-ratatui",
|
|
1126
1128
|
"plotui-term",
|
|
@@ -1129,18 +1131,18 @@ dependencies = [
|
|
|
1129
1131
|
|
|
1130
1132
|
[[package]]
|
|
1131
1133
|
name = "plotui-bind"
|
|
1132
|
-
version = "0.
|
|
1134
|
+
version = "0.5.0"
|
|
1133
1135
|
dependencies = [
|
|
1134
1136
|
"plotui-core",
|
|
1135
1137
|
]
|
|
1136
1138
|
|
|
1137
1139
|
[[package]]
|
|
1138
1140
|
name = "plotui-core"
|
|
1139
|
-
version = "0.
|
|
1141
|
+
version = "0.5.0"
|
|
1140
1142
|
|
|
1141
1143
|
[[package]]
|
|
1142
1144
|
name = "plotui-ffi"
|
|
1143
|
-
version = "0.
|
|
1145
|
+
version = "0.5.0"
|
|
1144
1146
|
dependencies = [
|
|
1145
1147
|
"cbindgen",
|
|
1146
1148
|
"plotui-bind",
|
|
@@ -1151,7 +1153,7 @@ dependencies = [
|
|
|
1151
1153
|
|
|
1152
1154
|
[[package]]
|
|
1153
1155
|
name = "plotui-protocol"
|
|
1154
|
-
version = "0.
|
|
1156
|
+
version = "0.5.0"
|
|
1155
1157
|
dependencies = [
|
|
1156
1158
|
"base64",
|
|
1157
1159
|
"flate2",
|
|
@@ -1160,7 +1162,7 @@ dependencies = [
|
|
|
1160
1162
|
|
|
1161
1163
|
[[package]]
|
|
1162
1164
|
name = "plotui-py"
|
|
1163
|
-
version = "0.
|
|
1165
|
+
version = "0.5.0"
|
|
1164
1166
|
dependencies = [
|
|
1165
1167
|
"numpy",
|
|
1166
1168
|
"plotui-bind",
|
|
@@ -1172,7 +1174,7 @@ dependencies = [
|
|
|
1172
1174
|
|
|
1173
1175
|
[[package]]
|
|
1174
1176
|
name = "plotui-ratatui"
|
|
1175
|
-
version = "0.
|
|
1177
|
+
version = "0.5.0"
|
|
1176
1178
|
dependencies = [
|
|
1177
1179
|
"crossterm",
|
|
1178
1180
|
"plotui-core",
|
|
@@ -1183,7 +1185,7 @@ dependencies = [
|
|
|
1183
1185
|
|
|
1184
1186
|
[[package]]
|
|
1185
1187
|
name = "plotui-term"
|
|
1186
|
-
version = "0.
|
|
1188
|
+
version = "0.5.0"
|
|
1187
1189
|
dependencies = [
|
|
1188
1190
|
"plotui-core",
|
|
1189
1191
|
"plotui-protocol",
|
|
@@ -1192,7 +1194,7 @@ dependencies = [
|
|
|
1192
1194
|
|
|
1193
1195
|
[[package]]
|
|
1194
1196
|
name = "plotui-wasm"
|
|
1195
|
-
version = "0.
|
|
1197
|
+
version = "0.5.0"
|
|
1196
1198
|
dependencies = [
|
|
1197
1199
|
"plotui-bind",
|
|
1198
1200
|
"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.
|
|
6
|
+
version = "0.5.0"
|
|
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.
|
|
18
|
-
plotui-protocol = { version = "0.
|
|
19
|
-
plotui-term = { version = "0.
|
|
20
|
-
plotui-bind = { version = "0.
|
|
21
|
-
plotui-ratatui = { version = "0.
|
|
17
|
+
plotui-core = { version = "0.5.0", path = "crates/plotui-core" }
|
|
18
|
+
plotui-protocol = { version = "0.5.0", path = "crates/plotui-protocol" }
|
|
19
|
+
plotui-term = { version = "0.5.0", path = "crates/plotui-term" }
|
|
20
|
+
plotui-bind = { version = "0.5.0", path = "crates/plotui-bind" }
|
|
21
|
+
plotui-ratatui = { version = "0.5.0", path = "crates/plotui-ratatui" }
|
|
22
22
|
ratatui = { version = "0.30", default-features = false }
|
|
23
23
|
crossterm = "0.29"
|
|
24
24
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: plotui
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Requires-Dist: textual>=0.60 ; extra == 'textual'
|
|
5
5
|
Provides-Extra: textual
|
|
6
6
|
License-File: LICENSE
|
|
@@ -21,9 +21,12 @@ drops into a [Textual](https://textual.textualize.io/),
|
|
|
21
21
|
[Bubble Tea](https://github.com/charmbracelet/bubbletea) app as a first-class
|
|
22
22
|
widget, with the rendering engine written in Rust so it stays fast in 2D and 3D.
|
|
23
23
|
|
|
24
|
-
> Status: **early scaffold.** Working today: 2D scatter/line/bar
|
|
25
|
-
>
|
|
26
|
-
>
|
|
24
|
+
> Status: **early scaffold.** Working today: 2D scatter/line/step/bar,
|
|
25
|
+
> histogram, box, heatmap, and band charts with axes, ticks, titles,
|
|
26
|
+
> explicit ranges, log scales, categorical labels, a colorbar and a legend;
|
|
27
|
+
> DAG/pipeline graphs with a layered layout and a DOT subset reader; a 3D
|
|
28
|
+
> scatter/graph/surface/mesh engine; a Kitty-image raw demo; and a Textual
|
|
29
|
+
> widget.
|
|
27
30
|
|
|
28
31
|
## Architecture
|
|
29
32
|
|
|
@@ -32,6 +35,10 @@ terminal.** It has no event loop and no input handling — the TUI framework
|
|
|
32
35
|
(Textual, Ratatui, or Bubble Tea) owns the loop, forwards input to the
|
|
33
36
|
camera, and asks for a frame.
|
|
34
37
|
|
|
38
|
+
```
|
|
39
|
+
f64 data ─▶ camera + rasterizer ─▶ RGBA buffer ─▶ Kitty escape bytes ─▶ your terminal's cell grid
|
|
40
|
+
```
|
|
41
|
+
|
|
35
42
|
```
|
|
36
43
|
crates/
|
|
37
44
|
plotui-core/ pure engine: data model, 3D camera, rasterizer → RGBA
|
|
@@ -49,7 +56,9 @@ examples/ raw_demo.py (Kitty images), textual_demo.py
|
|
|
49
56
|
```
|
|
50
57
|
|
|
51
58
|
`core` and `protocol` are pure and I/O-free, so the same engine can back every
|
|
52
|
-
frontend and be unit-tested by hashing pixel buffers.
|
|
59
|
+
frontend and be unit-tested by hashing pixel buffers. The protocol layer emits
|
|
60
|
+
Kitty graphics escapes — chunked, placement-aware, and wrapped for tmux
|
|
61
|
+
passthrough — as pure functions of the RGBA frame.
|
|
53
62
|
|
|
54
63
|
## Integrations
|
|
55
64
|
|
|
@@ -85,11 +94,41 @@ cargo binstall plotui # prebuilt, via cargo-binstall
|
|
|
85
94
|
```
|
|
86
95
|
|
|
87
96
|
```bash
|
|
88
|
-
seq 1 100 | awk '{print $1, sin($1/10)}' | plotui line
|
|
97
|
+
seq 1 100 | LC_ALL=C awk '{print $1, sin($1/10)}' | plotui line
|
|
89
98
|
plotui scatter -H -d, data.csv # header row + comma-delimited
|
|
90
|
-
plotui bar counts.tsv
|
|
99
|
+
plotui bar counts.tsv # --horizontal, --stack, --group
|
|
100
|
+
plotui step states.txt # holds its value between samples
|
|
101
|
+
plotui hist samples.txt # binned automatically
|
|
102
|
+
plotui box -H measurements.tsv # one box per column
|
|
103
|
+
plotui dag pipeline.dot # a DAG from a DOT file; hover a task
|
|
104
|
+
# to light everything it waits on
|
|
105
|
+
plotui line --log-y --title "queue depth" \
|
|
106
|
+
--x-title minute --y-title items # titles and log scales
|
|
107
|
+
plotui line --x-range 0:100 --y-range 0:1 # pin an extent, LO:HI
|
|
108
|
+
tail -f app.log | LC_ALL=C awk '{print $2}' \
|
|
109
|
+
| plotui line -f --window 200 # live, on the last 200 samples
|
|
110
|
+
plotui example scatter # built-in demo scenes, no data needed
|
|
111
|
+
plotui example deps # plotui's own dependency graph, laid
|
|
112
|
+
# out live by a force simulation
|
|
113
|
+
plotui example pipeline # a nightly forecast DAG, running
|
|
91
114
|
```
|
|
92
115
|
|
|
116
|
+
`--follow` (`-f`) keeps the reader open instead of plotting once at EOF: rows
|
|
117
|
+
are parsed as they arrive and appended in place, so the chart grows without a
|
|
118
|
+
redraw from scratch and pan/zoom survive. It needs piped input and a terminal
|
|
119
|
+
to draw into — a malformed line is skipped rather than fatal, and the count is
|
|
120
|
+
reported when you quit. The shape of the input (delimiter, column count,
|
|
121
|
+
whether x is a calendar) is settled by the first row and held for the rest of
|
|
122
|
+
the stream.
|
|
123
|
+
|
|
124
|
+
On a long feed the whole run compresses into a sliver, so `--window <N>` keeps
|
|
125
|
+
the view on the last N samples and `--last <span>` on the last `30s` / `5m` /
|
|
126
|
+
`2h` of x. Neither drops data: the window is a *view*, drawn on the range
|
|
127
|
+
slider (which a window switches on) against the entire run, so you can drag
|
|
128
|
+
back through everything that has arrived. Doing so hands the view over — a
|
|
129
|
+
reader who has scrolled back to an incident does not want the next row to
|
|
130
|
+
yank them forward — and **f** goes live again, jumping to the head.
|
|
131
|
+
|
|
93
132
|
Like every plotui frontend, the CLI needs a terminal with Kitty graphics
|
|
94
133
|
(supported terminals below); elsewhere it prints a notice and exits.
|
|
95
134
|
|
|
@@ -128,7 +167,8 @@ terminals, never a degraded plot. Override with
|
|
|
128
167
|
from plotui import Plot
|
|
129
168
|
|
|
130
169
|
# 2D: axes, ticks, and a legend appear automatically. Traces added without a
|
|
131
|
-
# color take
|
|
170
|
+
# color take colorway slots in fixed order; `name=` puts a series in the
|
|
171
|
+
# legend. Colors accept (r, g, b) tuples or shorthands: "#e63c78", "red".
|
|
132
172
|
plot = Plot()
|
|
133
173
|
plot.add_line(xs, ys, name="forecast")
|
|
134
174
|
plot.add_scatter(xs2, ys2, name="observed")
|
|
@@ -140,9 +180,42 @@ plot.add_bar(xs3, heights)
|
|
|
140
180
|
plot.add_line(xs, tokens, name="tokens", axis="y2")
|
|
141
181
|
plot.add_line(xs, cpu_minutes, name="cpu min", axis="y3")
|
|
142
182
|
|
|
183
|
+
# Titles: each buys its own margin, so the plot area shrinks rather than
|
|
184
|
+
# drawing over the data. The y title is drawn rotated in the left margin.
|
|
185
|
+
plot.set_title("p99 latency")
|
|
186
|
+
plot.set_x_title("requests")
|
|
187
|
+
plot.set_y_title("ms")
|
|
188
|
+
|
|
189
|
+
# Ranges and scales: an explicit range pins the *extent* only — no autoscale
|
|
190
|
+
# padding, and zoom/pan still compose on top of it (unlike set_x_window,
|
|
191
|
+
# which is the live window and supersedes the camera). A log axis ticks in
|
|
192
|
+
# powers of ten; values at or below zero have no log coordinate and neither
|
|
193
|
+
# set the range nor draw.
|
|
194
|
+
plot.set_x_range((0, 100)) # None restores autoscale
|
|
195
|
+
plot.set_y_range((0.1, 1e4))
|
|
196
|
+
plot.set_y_log(True) # set_x_log for x
|
|
197
|
+
|
|
198
|
+
# DAGs and pipelines: labelled boxes wired by arrows, laid out by rank. Node
|
|
199
|
+
# centres are data coordinates; each box is sized in pixels from its label,
|
|
200
|
+
# so zooming spreads the graph apart while the text stays readable.
|
|
201
|
+
from plotui import LayeredLayout, from_dot, reachable
|
|
202
|
+
|
|
203
|
+
layout = LayeredLayout(len(tasks), edges) # rankdir="TB" or "LR"
|
|
204
|
+
plot = Plot()
|
|
205
|
+
h = plot.add_graph2d(*layout.positions(), edges,
|
|
206
|
+
labels=tasks, routes=layout.routes())
|
|
207
|
+
plot.set_graph_colors(h, states) # repaint as the run advances
|
|
208
|
+
lit = reachable(len(tasks), edges, hovered) # everything it waits on
|
|
209
|
+
plot = from_dot(open("pipeline.dot").read()) # or straight from DOT
|
|
210
|
+
|
|
143
211
|
# 3D: any 3D trace switches the plot to the orbit camera.
|
|
144
212
|
plot = Plot()
|
|
145
|
-
plot.add_scatter3d(xs, ys, zs,
|
|
213
|
+
plot.add_scatter3d(xs, ys, zs, name="Cluster A") # colors from the colorway
|
|
214
|
+
|
|
215
|
+
# Colorways: the default sequence is pink/cyan/orange-first; swap it with a
|
|
216
|
+
# built-in name or your own list before adding traces.
|
|
217
|
+
plot.set_colorway("vivid") # "plotui", "muted", "vivid"
|
|
218
|
+
plot.set_colorway(["#e63c78", "cyan", (240, 161, 60)])
|
|
146
219
|
|
|
147
220
|
# Streaming: every add_* returns a trace handle. Append through it instead
|
|
148
221
|
# of rebuilding — O(new points), autoscale follows; numpy arrays are read
|
|
@@ -202,40 +275,14 @@ interaction in a subclass, override the `apply_rotate` / `apply_pan` /
|
|
|
202
275
|
routes through — do **not** override the Textual `on_*` handlers (Textual
|
|
203
276
|
dispatches those to every class in the MRO, so both would run).
|
|
204
277
|
|
|
205
|
-
## Roadmap
|
|
206
|
-
|
|
207
|
-
- [x] Flicker-free Kitty placement via Unicode-placeholder virtual placement
|
|
208
|
-
(fixed image id, atomic replace) — wire the pixel path into the Textual widget
|
|
209
|
-
- [x] 2D traces: scatter, line, bar; axes, ticks, tick labels, legend
|
|
210
|
-
- [x] Independent right-hand y-axes (`axis="y2"`/`"y3"`) with tinted tick labels
|
|
211
|
-
- [ ] 2D step trace; axis titles; time-formatted x ticks
|
|
212
|
-
- [ ] 3D surface / mesh; axis cube with labels
|
|
213
|
-
- [x] Interactive hover / pick for 3D graph nodes *and* edges (opt-in via
|
|
214
|
-
`PlotWidget(..., pickable=True)`: hover lights the element up white,
|
|
215
|
-
click posts `ElementPicked`)
|
|
216
|
-
- [ ] Hover / pick for 2D traces; spatial index for large graphs
|
|
217
|
-
- [x] Streaming append: trace handles, `extend`, `set_visible`, incremental bounds
|
|
218
|
-
- [x] numpy fast-path input (one bulk copy, no per-element conversion)
|
|
219
|
-
- [ ] Rolling window (`max_points`) for endless streams
|
|
220
|
-
- [x] Graceful render-path auto-detection (placeholder / direct Kitty, with a
|
|
221
|
-
supported-terminals notice elsewhere and a `PLOTUI_RENDER` override)
|
|
222
|
-
- [ ] Sixel + iTerm2 OSC 1337 encoders for terminals without Kitty graphics
|
|
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
|
|
226
|
-
- [x] Ratatui frontend (native): `plotui-ratatui` — StatefulWidget + app-owned
|
|
227
|
-
PlotState, full parity with the Textual widget
|
|
228
|
-
(`cargo run -p plotui-ratatui --example demo`)
|
|
229
|
-
- [x] Bubble Tea frontend (cgo): `go/` bindings over the `plotui-ffi` C ABI +
|
|
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`
|
|
236
|
-
- [ ] Prebuilt static libs for the Go bindings (today: local source build)
|
|
237
|
-
|
|
238
278
|
## License
|
|
239
279
|
|
|
240
|
-
MIT
|
|
280
|
+
MIT, except for one embedded asset: chart text is set in
|
|
281
|
+
[Martian Mono](https://github.com/evilmartians/mono) (Copyright 2020 The
|
|
282
|
+
Martian Mono Project Authors), used under the SIL Open Font License 1.1. The
|
|
283
|
+
glyph outlines are compiled into `plotui-core` as
|
|
284
|
+
`crates/plotui-core/src/glyphs.rs`; the license travels with them in
|
|
285
|
+
`crates/plotui-core/fonts/MartianMono-OFL.txt`. The font carries no Reserved
|
|
286
|
+
Font Name, and nothing about the OFL reaches your code — it covers the font
|
|
287
|
+
data, not the crate.
|
|
241
288
|
|
|
@@ -9,9 +9,12 @@ drops into a [Textual](https://textual.textualize.io/),
|
|
|
9
9
|
[Bubble Tea](https://github.com/charmbracelet/bubbletea) app as a first-class
|
|
10
10
|
widget, with the rendering engine written in Rust so it stays fast in 2D and 3D.
|
|
11
11
|
|
|
12
|
-
> Status: **early scaffold.** Working today: 2D scatter/line/bar
|
|
13
|
-
>
|
|
14
|
-
>
|
|
12
|
+
> Status: **early scaffold.** Working today: 2D scatter/line/step/bar,
|
|
13
|
+
> histogram, box, heatmap, and band charts with axes, ticks, titles,
|
|
14
|
+
> explicit ranges, log scales, categorical labels, a colorbar and a legend;
|
|
15
|
+
> DAG/pipeline graphs with a layered layout and a DOT subset reader; a 3D
|
|
16
|
+
> scatter/graph/surface/mesh engine; a Kitty-image raw demo; and a Textual
|
|
17
|
+
> widget.
|
|
15
18
|
|
|
16
19
|
## Architecture
|
|
17
20
|
|
|
@@ -20,6 +23,10 @@ terminal.** It has no event loop and no input handling — the TUI framework
|
|
|
20
23
|
(Textual, Ratatui, or Bubble Tea) owns the loop, forwards input to the
|
|
21
24
|
camera, and asks for a frame.
|
|
22
25
|
|
|
26
|
+
```
|
|
27
|
+
f64 data ─▶ camera + rasterizer ─▶ RGBA buffer ─▶ Kitty escape bytes ─▶ your terminal's cell grid
|
|
28
|
+
```
|
|
29
|
+
|
|
23
30
|
```
|
|
24
31
|
crates/
|
|
25
32
|
plotui-core/ pure engine: data model, 3D camera, rasterizer → RGBA
|
|
@@ -37,7 +44,9 @@ examples/ raw_demo.py (Kitty images), textual_demo.py
|
|
|
37
44
|
```
|
|
38
45
|
|
|
39
46
|
`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.
|
|
47
|
+
frontend and be unit-tested by hashing pixel buffers. The protocol layer emits
|
|
48
|
+
Kitty graphics escapes — chunked, placement-aware, and wrapped for tmux
|
|
49
|
+
passthrough — as pure functions of the RGBA frame.
|
|
41
50
|
|
|
42
51
|
## Integrations
|
|
43
52
|
|
|
@@ -73,11 +82,41 @@ cargo binstall plotui # prebuilt, via cargo-binstall
|
|
|
73
82
|
```
|
|
74
83
|
|
|
75
84
|
```bash
|
|
76
|
-
seq 1 100 | awk '{print $1, sin($1/10)}' | plotui line
|
|
85
|
+
seq 1 100 | LC_ALL=C awk '{print $1, sin($1/10)}' | plotui line
|
|
77
86
|
plotui scatter -H -d, data.csv # header row + comma-delimited
|
|
78
|
-
plotui bar counts.tsv
|
|
87
|
+
plotui bar counts.tsv # --horizontal, --stack, --group
|
|
88
|
+
plotui step states.txt # holds its value between samples
|
|
89
|
+
plotui hist samples.txt # binned automatically
|
|
90
|
+
plotui box -H measurements.tsv # one box per column
|
|
91
|
+
plotui dag pipeline.dot # a DAG from a DOT file; hover a task
|
|
92
|
+
# to light everything it waits on
|
|
93
|
+
plotui line --log-y --title "queue depth" \
|
|
94
|
+
--x-title minute --y-title items # titles and log scales
|
|
95
|
+
plotui line --x-range 0:100 --y-range 0:1 # pin an extent, LO:HI
|
|
96
|
+
tail -f app.log | LC_ALL=C awk '{print $2}' \
|
|
97
|
+
| plotui line -f --window 200 # live, on the last 200 samples
|
|
98
|
+
plotui example scatter # built-in demo scenes, no data needed
|
|
99
|
+
plotui example deps # plotui's own dependency graph, laid
|
|
100
|
+
# out live by a force simulation
|
|
101
|
+
plotui example pipeline # a nightly forecast DAG, running
|
|
79
102
|
```
|
|
80
103
|
|
|
104
|
+
`--follow` (`-f`) keeps the reader open instead of plotting once at EOF: rows
|
|
105
|
+
are parsed as they arrive and appended in place, so the chart grows without a
|
|
106
|
+
redraw from scratch and pan/zoom survive. It needs piped input and a terminal
|
|
107
|
+
to draw into — a malformed line is skipped rather than fatal, and the count is
|
|
108
|
+
reported when you quit. The shape of the input (delimiter, column count,
|
|
109
|
+
whether x is a calendar) is settled by the first row and held for the rest of
|
|
110
|
+
the stream.
|
|
111
|
+
|
|
112
|
+
On a long feed the whole run compresses into a sliver, so `--window <N>` keeps
|
|
113
|
+
the view on the last N samples and `--last <span>` on the last `30s` / `5m` /
|
|
114
|
+
`2h` of x. Neither drops data: the window is a *view*, drawn on the range
|
|
115
|
+
slider (which a window switches on) against the entire run, so you can drag
|
|
116
|
+
back through everything that has arrived. Doing so hands the view over — a
|
|
117
|
+
reader who has scrolled back to an incident does not want the next row to
|
|
118
|
+
yank them forward — and **f** goes live again, jumping to the head.
|
|
119
|
+
|
|
81
120
|
Like every plotui frontend, the CLI needs a terminal with Kitty graphics
|
|
82
121
|
(supported terminals below); elsewhere it prints a notice and exits.
|
|
83
122
|
|
|
@@ -116,7 +155,8 @@ terminals, never a degraded plot. Override with
|
|
|
116
155
|
from plotui import Plot
|
|
117
156
|
|
|
118
157
|
# 2D: axes, ticks, and a legend appear automatically. Traces added without a
|
|
119
|
-
# color take
|
|
158
|
+
# color take colorway slots in fixed order; `name=` puts a series in the
|
|
159
|
+
# legend. Colors accept (r, g, b) tuples or shorthands: "#e63c78", "red".
|
|
120
160
|
plot = Plot()
|
|
121
161
|
plot.add_line(xs, ys, name="forecast")
|
|
122
162
|
plot.add_scatter(xs2, ys2, name="observed")
|
|
@@ -128,9 +168,42 @@ plot.add_bar(xs3, heights)
|
|
|
128
168
|
plot.add_line(xs, tokens, name="tokens", axis="y2")
|
|
129
169
|
plot.add_line(xs, cpu_minutes, name="cpu min", axis="y3")
|
|
130
170
|
|
|
171
|
+
# Titles: each buys its own margin, so the plot area shrinks rather than
|
|
172
|
+
# drawing over the data. The y title is drawn rotated in the left margin.
|
|
173
|
+
plot.set_title("p99 latency")
|
|
174
|
+
plot.set_x_title("requests")
|
|
175
|
+
plot.set_y_title("ms")
|
|
176
|
+
|
|
177
|
+
# Ranges and scales: an explicit range pins the *extent* only — no autoscale
|
|
178
|
+
# padding, and zoom/pan still compose on top of it (unlike set_x_window,
|
|
179
|
+
# which is the live window and supersedes the camera). A log axis ticks in
|
|
180
|
+
# powers of ten; values at or below zero have no log coordinate and neither
|
|
181
|
+
# set the range nor draw.
|
|
182
|
+
plot.set_x_range((0, 100)) # None restores autoscale
|
|
183
|
+
plot.set_y_range((0.1, 1e4))
|
|
184
|
+
plot.set_y_log(True) # set_x_log for x
|
|
185
|
+
|
|
186
|
+
# DAGs and pipelines: labelled boxes wired by arrows, laid out by rank. Node
|
|
187
|
+
# centres are data coordinates; each box is sized in pixels from its label,
|
|
188
|
+
# so zooming spreads the graph apart while the text stays readable.
|
|
189
|
+
from plotui import LayeredLayout, from_dot, reachable
|
|
190
|
+
|
|
191
|
+
layout = LayeredLayout(len(tasks), edges) # rankdir="TB" or "LR"
|
|
192
|
+
plot = Plot()
|
|
193
|
+
h = plot.add_graph2d(*layout.positions(), edges,
|
|
194
|
+
labels=tasks, routes=layout.routes())
|
|
195
|
+
plot.set_graph_colors(h, states) # repaint as the run advances
|
|
196
|
+
lit = reachable(len(tasks), edges, hovered) # everything it waits on
|
|
197
|
+
plot = from_dot(open("pipeline.dot").read()) # or straight from DOT
|
|
198
|
+
|
|
131
199
|
# 3D: any 3D trace switches the plot to the orbit camera.
|
|
132
200
|
plot = Plot()
|
|
133
|
-
plot.add_scatter3d(xs, ys, zs,
|
|
201
|
+
plot.add_scatter3d(xs, ys, zs, name="Cluster A") # colors from the colorway
|
|
202
|
+
|
|
203
|
+
# Colorways: the default sequence is pink/cyan/orange-first; swap it with a
|
|
204
|
+
# built-in name or your own list before adding traces.
|
|
205
|
+
plot.set_colorway("vivid") # "plotui", "muted", "vivid"
|
|
206
|
+
plot.set_colorway(["#e63c78", "cyan", (240, 161, 60)])
|
|
134
207
|
|
|
135
208
|
# Streaming: every add_* returns a trace handle. Append through it instead
|
|
136
209
|
# of rebuilding — O(new points), autoscale follows; numpy arrays are read
|
|
@@ -190,39 +263,13 @@ interaction in a subclass, override the `apply_rotate` / `apply_pan` /
|
|
|
190
263
|
routes through — do **not** override the Textual `on_*` handlers (Textual
|
|
191
264
|
dispatches those to every class in the MRO, so both would run).
|
|
192
265
|
|
|
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
266
|
## License
|
|
227
267
|
|
|
228
|
-
MIT
|
|
268
|
+
MIT, except for one embedded asset: chart text is set in
|
|
269
|
+
[Martian Mono](https://github.com/evilmartians/mono) (Copyright 2020 The
|
|
270
|
+
Martian Mono Project Authors), used under the SIL Open Font License 1.1. The
|
|
271
|
+
glyph outlines are compiled into `plotui-core` as
|
|
272
|
+
`crates/plotui-core/src/glyphs.rs`; the license travels with them in
|
|
273
|
+
`crates/plotui-core/fonts/MartianMono-OFL.txt`. The font carries no Reserved
|
|
274
|
+
Font Name, and nothing about the OFL reaches your code — it covers the font
|
|
275
|
+
data, not the crate.
|
|
@@ -9,9 +9,12 @@ drops into a [Textual](https://textual.textualize.io/),
|
|
|
9
9
|
[Bubble Tea](https://github.com/charmbracelet/bubbletea) app as a first-class
|
|
10
10
|
widget, with the rendering engine written in Rust so it stays fast in 2D and 3D.
|
|
11
11
|
|
|
12
|
-
> Status: **early scaffold.** Working today: 2D scatter/line/bar
|
|
13
|
-
>
|
|
14
|
-
>
|
|
12
|
+
> Status: **early scaffold.** Working today: 2D scatter/line/step/bar,
|
|
13
|
+
> histogram, box, heatmap, and band charts with axes, ticks, titles,
|
|
14
|
+
> explicit ranges, log scales, categorical labels, a colorbar and a legend;
|
|
15
|
+
> DAG/pipeline graphs with a layered layout and a DOT subset reader; a 3D
|
|
16
|
+
> scatter/graph/surface/mesh engine; a Kitty-image raw demo; and a Textual
|
|
17
|
+
> widget.
|
|
15
18
|
|
|
16
19
|
## Architecture
|
|
17
20
|
|
|
@@ -20,6 +23,10 @@ terminal.** It has no event loop and no input handling — the TUI framework
|
|
|
20
23
|
(Textual, Ratatui, or Bubble Tea) owns the loop, forwards input to the
|
|
21
24
|
camera, and asks for a frame.
|
|
22
25
|
|
|
26
|
+
```
|
|
27
|
+
f64 data ─▶ camera + rasterizer ─▶ RGBA buffer ─▶ Kitty escape bytes ─▶ your terminal's cell grid
|
|
28
|
+
```
|
|
29
|
+
|
|
23
30
|
```
|
|
24
31
|
crates/
|
|
25
32
|
plotui-core/ pure engine: data model, 3D camera, rasterizer → RGBA
|
|
@@ -37,7 +44,9 @@ examples/ raw_demo.py (Kitty images), textual_demo.py
|
|
|
37
44
|
```
|
|
38
45
|
|
|
39
46
|
`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.
|
|
47
|
+
frontend and be unit-tested by hashing pixel buffers. The protocol layer emits
|
|
48
|
+
Kitty graphics escapes — chunked, placement-aware, and wrapped for tmux
|
|
49
|
+
passthrough — as pure functions of the RGBA frame.
|
|
41
50
|
|
|
42
51
|
## Integrations
|
|
43
52
|
|
|
@@ -73,11 +82,41 @@ cargo binstall plotui # prebuilt, via cargo-binstall
|
|
|
73
82
|
```
|
|
74
83
|
|
|
75
84
|
```bash
|
|
76
|
-
seq 1 100 | awk '{print $1, sin($1/10)}' | plotui line
|
|
85
|
+
seq 1 100 | LC_ALL=C awk '{print $1, sin($1/10)}' | plotui line
|
|
77
86
|
plotui scatter -H -d, data.csv # header row + comma-delimited
|
|
78
|
-
plotui bar counts.tsv
|
|
87
|
+
plotui bar counts.tsv # --horizontal, --stack, --group
|
|
88
|
+
plotui step states.txt # holds its value between samples
|
|
89
|
+
plotui hist samples.txt # binned automatically
|
|
90
|
+
plotui box -H measurements.tsv # one box per column
|
|
91
|
+
plotui dag pipeline.dot # a DAG from a DOT file; hover a task
|
|
92
|
+
# to light everything it waits on
|
|
93
|
+
plotui line --log-y --title "queue depth" \
|
|
94
|
+
--x-title minute --y-title items # titles and log scales
|
|
95
|
+
plotui line --x-range 0:100 --y-range 0:1 # pin an extent, LO:HI
|
|
96
|
+
tail -f app.log | LC_ALL=C awk '{print $2}' \
|
|
97
|
+
| plotui line -f --window 200 # live, on the last 200 samples
|
|
98
|
+
plotui example scatter # built-in demo scenes, no data needed
|
|
99
|
+
plotui example deps # plotui's own dependency graph, laid
|
|
100
|
+
# out live by a force simulation
|
|
101
|
+
plotui example pipeline # a nightly forecast DAG, running
|
|
79
102
|
```
|
|
80
103
|
|
|
104
|
+
`--follow` (`-f`) keeps the reader open instead of plotting once at EOF: rows
|
|
105
|
+
are parsed as they arrive and appended in place, so the chart grows without a
|
|
106
|
+
redraw from scratch and pan/zoom survive. It needs piped input and a terminal
|
|
107
|
+
to draw into — a malformed line is skipped rather than fatal, and the count is
|
|
108
|
+
reported when you quit. The shape of the input (delimiter, column count,
|
|
109
|
+
whether x is a calendar) is settled by the first row and held for the rest of
|
|
110
|
+
the stream.
|
|
111
|
+
|
|
112
|
+
On a long feed the whole run compresses into a sliver, so `--window <N>` keeps
|
|
113
|
+
the view on the last N samples and `--last <span>` on the last `30s` / `5m` /
|
|
114
|
+
`2h` of x. Neither drops data: the window is a *view*, drawn on the range
|
|
115
|
+
slider (which a window switches on) against the entire run, so you can drag
|
|
116
|
+
back through everything that has arrived. Doing so hands the view over — a
|
|
117
|
+
reader who has scrolled back to an incident does not want the next row to
|
|
118
|
+
yank them forward — and **f** goes live again, jumping to the head.
|
|
119
|
+
|
|
81
120
|
Like every plotui frontend, the CLI needs a terminal with Kitty graphics
|
|
82
121
|
(supported terminals below); elsewhere it prints a notice and exits.
|
|
83
122
|
|
|
@@ -116,7 +155,8 @@ terminals, never a degraded plot. Override with
|
|
|
116
155
|
from plotui import Plot
|
|
117
156
|
|
|
118
157
|
# 2D: axes, ticks, and a legend appear automatically. Traces added without a
|
|
119
|
-
# color take
|
|
158
|
+
# color take colorway slots in fixed order; `name=` puts a series in the
|
|
159
|
+
# legend. Colors accept (r, g, b) tuples or shorthands: "#e63c78", "red".
|
|
120
160
|
plot = Plot()
|
|
121
161
|
plot.add_line(xs, ys, name="forecast")
|
|
122
162
|
plot.add_scatter(xs2, ys2, name="observed")
|
|
@@ -128,9 +168,42 @@ plot.add_bar(xs3, heights)
|
|
|
128
168
|
plot.add_line(xs, tokens, name="tokens", axis="y2")
|
|
129
169
|
plot.add_line(xs, cpu_minutes, name="cpu min", axis="y3")
|
|
130
170
|
|
|
171
|
+
# Titles: each buys its own margin, so the plot area shrinks rather than
|
|
172
|
+
# drawing over the data. The y title is drawn rotated in the left margin.
|
|
173
|
+
plot.set_title("p99 latency")
|
|
174
|
+
plot.set_x_title("requests")
|
|
175
|
+
plot.set_y_title("ms")
|
|
176
|
+
|
|
177
|
+
# Ranges and scales: an explicit range pins the *extent* only — no autoscale
|
|
178
|
+
# padding, and zoom/pan still compose on top of it (unlike set_x_window,
|
|
179
|
+
# which is the live window and supersedes the camera). A log axis ticks in
|
|
180
|
+
# powers of ten; values at or below zero have no log coordinate and neither
|
|
181
|
+
# set the range nor draw.
|
|
182
|
+
plot.set_x_range((0, 100)) # None restores autoscale
|
|
183
|
+
plot.set_y_range((0.1, 1e4))
|
|
184
|
+
plot.set_y_log(True) # set_x_log for x
|
|
185
|
+
|
|
186
|
+
# DAGs and pipelines: labelled boxes wired by arrows, laid out by rank. Node
|
|
187
|
+
# centres are data coordinates; each box is sized in pixels from its label,
|
|
188
|
+
# so zooming spreads the graph apart while the text stays readable.
|
|
189
|
+
from plotui import LayeredLayout, from_dot, reachable
|
|
190
|
+
|
|
191
|
+
layout = LayeredLayout(len(tasks), edges) # rankdir="TB" or "LR"
|
|
192
|
+
plot = Plot()
|
|
193
|
+
h = plot.add_graph2d(*layout.positions(), edges,
|
|
194
|
+
labels=tasks, routes=layout.routes())
|
|
195
|
+
plot.set_graph_colors(h, states) # repaint as the run advances
|
|
196
|
+
lit = reachable(len(tasks), edges, hovered) # everything it waits on
|
|
197
|
+
plot = from_dot(open("pipeline.dot").read()) # or straight from DOT
|
|
198
|
+
|
|
131
199
|
# 3D: any 3D trace switches the plot to the orbit camera.
|
|
132
200
|
plot = Plot()
|
|
133
|
-
plot.add_scatter3d(xs, ys, zs,
|
|
201
|
+
plot.add_scatter3d(xs, ys, zs, name="Cluster A") # colors from the colorway
|
|
202
|
+
|
|
203
|
+
# Colorways: the default sequence is pink/cyan/orange-first; swap it with a
|
|
204
|
+
# built-in name or your own list before adding traces.
|
|
205
|
+
plot.set_colorway("vivid") # "plotui", "muted", "vivid"
|
|
206
|
+
plot.set_colorway(["#e63c78", "cyan", (240, 161, 60)])
|
|
134
207
|
|
|
135
208
|
# Streaming: every add_* returns a trace handle. Append through it instead
|
|
136
209
|
# of rebuilding — O(new points), autoscale follows; numpy arrays are read
|
|
@@ -190,39 +263,13 @@ interaction in a subclass, override the `apply_rotate` / `apply_pan` /
|
|
|
190
263
|
routes through — do **not** override the Textual `on_*` handlers (Textual
|
|
191
264
|
dispatches those to every class in the MRO, so both would run).
|
|
192
265
|
|
|
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
266
|
## License
|
|
227
267
|
|
|
228
|
-
MIT
|
|
268
|
+
MIT, except for one embedded asset: chart text is set in
|
|
269
|
+
[Martian Mono](https://github.com/evilmartians/mono) (Copyright 2020 The
|
|
270
|
+
Martian Mono Project Authors), used under the SIL Open Font License 1.1. The
|
|
271
|
+
glyph outlines are compiled into `plotui-core` as
|
|
272
|
+
`crates/plotui-core/src/glyphs.rs`; the license travels with them in
|
|
273
|
+
`crates/plotui-core/fonts/MartianMono-OFL.txt`. The font carries no Reserved
|
|
274
|
+
Font Name, and nothing about the OFL reaches your code — it covers the font
|
|
275
|
+
data, not the crate.
|