strip_plot(plot=...) and get_archiver_time_range(plot=...) draw as text (eco.utilities.text_charts) with plot='text', and by themselves where no window can appear (no display, ECO_NO_X=1); ECO_PLOT=text makes text the default for them and the tweak plot. One panel per channel over a shared time axis, values as steps; keys: arrows pan / zoom y, + - zoom time, a autoscale, f follow now / show all, space pause, q quit. A text strip chart runs in the session and returns what it collected.
6.2 KiB
Terminal plots
Plots normally go to Qt windows or notebook widgets. Where neither is possible, most commonly a plain ssh session without X, eco can render them as text in the terminal. This page records how that is done and what is still missing.
Using it
Three plots have a text version. They switch to it by themselves where no window can appear (plain ssh, ECO_NO_X=1), and on request otherwise:
mirror.strip_plot(plot="text") # live strip chart
mirror.get_archiver_time_range(start=-3600, plot="text") # history chart
mot.tweak(0.1, detectors=[diode], ui="terminal", plot="text")
export ECO_PLOT=text makes text the default for all of them (ECO_TWEAK_PLOT for the tweak alone). strip_plot works for any detector too, EPICS or not: from eco.utilities.strip_plot import strip_plot; strip_plot(sim.peak.signal, plot="text").
The strip and history charts draw one panel per channel over a shared time axis, values as steps, and take keys until q (the last picture stays):
| Key | Does |
|---|---|
| ← → | pan in time by a quarter of the shown span |
| + − | zoom in / out in time |
| ↑ ↓ | zoom in / out in y |
| a | autoscale y again |
| f | live: follow now again; history: show everything |
| space | live: pause / resume |
| q | quit |
A text strip chart runs in the session itself (no subprocess) and returns {label: (times, values)} of what it collected; the window's Record/Save buttons and "More channels..." have no text counterpart. In the windows, matplotlib's toolbar zooms and pans; the strip plot window's "Autoscale X/Y" buttons switch autoscaling back on after a zoom, "Window ×2 / /2" change the span.
Current approach: uniplot as a dependency
Terminal plots use uniplot (MIT, pure Python, needs only numpy and readchar). It is in the Bernina sfb312 environment (conda) but not declared in pyproject.toml.
Checked against uniplot 0.23.2 (2026-10), and sufficient for now:
plot_to_string(ys, xs, width=, height=, ...)returns the plot as a string, so eco controls where and when it is printed.- Several series per plot, with colors and
legend_labels/legend_placement. histogram(xs, bins=, ...).datetime64x values give time tick labels (10:00,10:10, ...), which covers archiver data.- Log axes (
x_as_log,y_as_log), units (x_unit,y_unit), gridlines, fixed ranges (x_min...y_max). - Character sets
BLOCK(2x2 quadrants, the default, renders cleanly in every font),BRAILLE(2x4, finer, but gaps in some fonts) andASCII.
uniplot sits behind eco.utilities.termplot, an eco-shaped layer that callers use instead of uniplot directly:
panel_stack()stacks plots over a shared x range, with x tick labels only under the last one.LiveRegion/LivePlotterkeep a plot just above the current terminal line and redraw it in place. Other code can keep rewriting a status line with\rmeanwhile, as the terminal tweaks do. While active,LiveRegionreplacessys.stdoutwith a proxy so it can tell whether anything else printed a newline. If so, it prints a fresh copy and restores the status line under it.
Users: the terminal tweak's detector plot (eco.widgets.tweak_recorder.terminal_plot_text), and the strip and history charts (eco.utilities.text_charts, which also has the key loop). Counters are meant to follow. If the gaps below start to hurt, replace the renderer with an own copy (MIT allows it; keep the copyright notice) without touching the callers.
Shortcomings to address in an own copy
Add to this list whenever a terminal plot runs into a limit.
- No subplots or shared axes. Stacks (the tweak recorder's detectors over adjustables) are built by concatenating one
plot_to_stringper panel. The x range has to be passed to every panel by hand (x_min/x_max). Repeated x tick labels can be avoided withx_labels=Falseon all but the bottom panel, but each panel still has its own frame, so a stack takes more height than it needs. - No y-axis title. Only
title(above) and units on the tick labels. In a stack the panel name has to go intotitle, which costs one line per panel. - Interactive pan/zoom only works on static data, and takes over the terminal.
plot(..., interactive=True)runs its own blockingreadkey()loop: arrows /hjkl/wasdpan,q/ Esc quits. That cannot update while new data arrives (live strip charts, counters, tweak), and it can't share the keyboard with another key loop. The tweak uses the same arrow keys. Worked around for the strip and history charts:eco.utilities.text_chartsruns its own key loop and passes the range to uniplot (x_min/x_max/y_min/y_max) on every redraw, live data included. The tweak plot still has no zoom (its arrow keys move the motor). - No managed live region.
plot_gen()redraws in place, but knows nothing about other output on the same terminal (status lines written with\r, log messages). eco needs one "live region" helper that owns the cursor movement for plots and status lines together. - Limited control over ticks.
x_labels/y_labelsonly switch the labels on or off; their number and position are chosen automatically. - Few ways to style points.
linescan be set per series (line or scatter), so a highlighted point such as the tweak's current one can be an extra scatter series with its own color. There are no marker shapes or sizes beyond that. - No error bars or bands (e.g. the recorder's per-point standard deviation).
Alternatives considered (2026-10)
- plotext: 6.x is an API rewrite, so code written for 5.x does not run on it. It is commented out in
sfb312. - plotille: maintained and braille-based, but no subplots and no advantage over uniplot's
BRAILLEmode. - termplotlib: needs a
gnuplotbinary. asciichartpy: y values only, no x. - timg (in
sfb312): not a plotting library. It shows images in the terminal: kitty / iTerm2 / sixel where the terminal supports them, colored half-blocks otherwise. This makes it a complementary high-fidelity path: render the matplotlib figure eco already draws to PNG and display it. tmux/screen usually block the image protocols. Not yet tried in practice.