Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package ttyplot for openSUSE:Factory checked in at 2026-07-15 16:40:43 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/ttyplot (Old) and /work/SRC/openSUSE:Factory/.ttyplot.new.1991 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "ttyplot" Wed Jul 15 16:40:43 2026 rev:5 rq:1365683 version:1.7.6 Changes: -------- --- /work/SRC/openSUSE:Factory/ttyplot/ttyplot.changes 2026-06-25 10:58:37.652318506 +0200 +++ /work/SRC/openSUSE:Factory/.ttyplot.new.1991/ttyplot.changes 2026-07-15 17:00:09.212160689 +0200 @@ -1,0 +2,16 @@ +Tue Jul 14 13:23:25 UTC 2026 - Martin Hauke <[email protected]> + +- Update to version 1.7.6 + New rendering modes + * braille line mode (-b) — high-resolution 2×4 dot plotting; two + lines distinguished by color. + * block / quadrant mode (-B) — 2×2 block-element plotting, + like -b + * fill (-f) — fill the area under the line in braille/block mode + (line 1). + * aalib ASCII-art mode (-A) — experimental smooth ASCII line + rendering, opt-in (make AA=1, requires aalib). + * updated dark color scheme. +- Build with support for aalib. + +------------------------------------------------------------------- Old: ---- ttyplot-1.7.5.tar.gz New: ---- ttyplot-1.7.6.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ ttyplot.spec ++++++ --- /var/tmp/diff_new_pack.sU7ICQ/_old 2026-07-15 17:00:09.896183886 +0200 +++ /var/tmp/diff_new_pack.sU7ICQ/_new 2026-07-15 17:00:09.896183886 +0200 @@ -18,12 +18,13 @@ Name: ttyplot -Version: 1.7.5 +Version: 1.7.6 Release: 0 Summary: Realtime plotting utility for terminals License: Apache-2.0 URL: https://github.com/tenox7/ttyplot Source: https://github.com/tenox7/ttyplot/archive/refs/tags/%{version}.tar.gz#/%{name}-%{version}.tar.gz +BuildRequires: aalib-devel BuildRequires: pkgconfig BuildRequires: pkgconfig(ncurses) @@ -37,7 +38,7 @@ %autosetup -p1 %build -%make_build CFLAGS="%{optflags} $(pkg-config --cflags --libs ncursesw)" +%make_build CFLAGS="%{optflags} $(pkg-config --cflags --libs ncursesw)" AA=1 %install %make_install DESTDIR=%{buildroot} PREFIX=%{_prefix} MANPREFIX=%{_mandir} ++++++ ttyplot-1.7.5.tar.gz -> ttyplot-1.7.6.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ttyplot-1.7.5/Makefile new/ttyplot-1.7.6/Makefile --- old/ttyplot-1.7.5/Makefile 2026-06-22 04:17:25.000000000 +0200 +++ new/ttyplot-1.7.6/Makefile 2026-07-14 05:38:07.000000000 +0200 @@ -1,4 +1,4 @@ -VERSION = 1.7.5 +VERSION = 1.7.6 DESTDIR ?= PREFIX ?= /usr/local MANPREFIX ?= $(PREFIX)/man @@ -7,6 +7,14 @@ CFLAGS += `pkg-config --cflags ncursesw` LDLIBS += `pkg-config --libs ncursesw` -lm +# Experimental, opt-in ASCII-art rendering backend (aalib). Off by default; +# build with `make AA=1` (requires aalib and its aalib-config helper). +# Written without GNU-only `ifdef` so this file also parses under BSD make (bmake). +AALIB_CPPFLAGS_1 = -DAALIB `aalib-config --cflags` +AALIB_LDLIBS_1 = `aalib-config --libs` +CPPFLAGS += $(AALIB_CPPFLAGS_$(AA)) +LDLIBS += $(AALIB_LDLIBS_$(AA)) + all: ttyplot stresstest install: ttyplot ttyplot.1 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ttyplot-1.7.5/README.md new/ttyplot-1.7.6/README.md --- old/ttyplot-1.7.5/README.md 2026-06-22 04:17:25.000000000 +0200 +++ new/ttyplot-1.7.6/README.md 2026-07-14 05:38:07.000000000 +0200 @@ -17,6 +17,14 @@ +experimental braille and block element support: + + + + + + + ## get <a href="https://repology.org/project/ttyplot/versions"> @@ -88,7 +96,7 @@ ### memory usage on macOS ``` -vm_stat 1 | awk '{ print int($2)*4096/1024^3; fflush(); }' | ttyplot -t "MacOS Memory Usage" -u GB +vm_stat 1 | awk '{ print int($2)/2^18; fflush(); }' | ttyplot -t "MacOS Memory Usage" -u GB ``` ### number of processes in running and io blocked state @@ -133,7 +141,7 @@ ### memory usage from rrdtool and collectd using awk ``` -{ while true; do rrdtool lastupdate /var/lib/collectd/rrd/$(hostname)/memory/memory-used.rrd | awk 'END { print ($NF)/1024/1024 }'; sleep 1; done } | ttyplot -m $(awk '/MemTotal/ { print ($2)/1024 }' /proc/meminfo) -t "Memoru Used" -u MB +{ while true; do rrdtool lastupdate /var/lib/collectd/rrd/$(hostname)/memory/memory-used.rrd | awk 'END { print ($NF)/1024/1024 }'; sleep 1; done } | ttyplot -m $(awk '/MemTotal/ { print ($2)/1024 }' /proc/meminfo) -t "Memory Used" -u MB ``` ### bitcoin price chart using curl and jq @@ -141,9 +149,10 @@ { while true; do curl -sL https://api.coindesk.com/v1/bpi/currentprice.json | jq .bpi.USD.rate_float; sleep 600; done } | ttyplot -t "bitcoin price" -u usd ``` -### stock quote chart +### stock price chart using curl and jq + ``` -{ while true; do curl -sL https://api.iextrading.com/1.0/stock/googl/price; echo; sleep 600; done } | ttyplot -t "google stock price" -u usd +{ while true; do curl -s "https://query1.finance.yahoo.com/v8/finance/chart/SPY" -H "User-Agent: Mozilla/5.0" | jq '.chart.result[0].meta.regularMarketPrice'; sleep 60; done } | ttyplot -t "SPY stock price" -u usd ``` ### prometheus load average via node_exporter @@ -223,11 +232,15 @@ ## flags ``` - ttyplot [-2] [-r] [-c plotchar] [-s scale] [-m max] [-M min] [-t title] [-u unit] + ttyplot [-2] [-b] [-B] [-f] [-r] [-c plotchar] [-s scale] [-m max] [-M min] [-t title] [-u unit] ttyplot -h ttyplot -v -2 read two values and draw two plots, the second one is in reverse video + -b (experimental) braille line drawing mode, two lines in color + -B (experimental) block elements drawing mode (quadrants), like -b + -f (experimental) fill area under braille/block line 1 + -A (experimental) aalib ASCII-art line mode, two lines in color (only if built with AA=1) -r rate of a counter (divide value by measured sample interval) -c character to use for plot line, eg @ # % . etc -e character to use for error line when value exceeds hardmax (default: e) @@ -246,8 +259,8 @@ Sixth value: min error indicator color (optional) Example: -C 1,2,3,4,5,6 or -C 1,2 or -C 1 Predefined color schemes: - -C dark1 Blue-cyan-yellow scheme for dark terminals - -C dark2 Purple-yellow-green scheme for dark terminals + -C dark1 Red-green lines for dark terminals + -C dark2 Blue-yellow lines for dark terminals -C light1 Green-blue-red scheme for light terminals -C light2 Blue-green-yellow scheme for light terminals Colors: 0=black, 1=red, 2=green, 3=yellow, 4=blue, 5=magenta, 6=cyan, 7=white @@ -256,6 +269,33 @@ ``` + + + +## experimental: braille / block line drawing + +> **experimental** — this feature and its flags may still change + +`-b` draws **braille** lines instead of block / reverse-video bars; `-B` does the same with **quadrant block elements** (`▖▗▘▙▚▛▜▝▞▟`). with `-2`: line 1 green, line 2 blue (no reverse video); `-f` fills under line 1; `-C n` sets line 1, `-C n/m` sets both. needs a UTF-8 locale and a capable font, else falls back. + +``` +ping 8.8.8.8 | sed -u 's/^.*time=//g; s/ ms//g' | ttyplot -b -t "ping to 8.8.8.8" -u ms +vmstat -n 1 | perl -lane 'BEGIN{$|=1} print "@F[0,1]"' | ttyplot -b -2 -f -t "procs in R and D" +``` + + +## experimental: aalib ASCII-art rendering + +> **very experimental / throwaway** — off by default, opt-in at build time + +`-A` draws smooth **7-bit ASCII** lines via [aalib](https://aa-project.sourceforge.net/aalib/) (think `aafire`), for dumb terminals that can't do braille/Unicode. same two-line color and `-f` fill as braille. not built by default: `make AA=1` (needs aalib + `aalib-config`, adds `-DAALIB -laa`). smoothest in a C locale (`LC_ALL=C`); UTF-8 folds down to approximate ASCII. + +``` +make AA=1 +vmstat -n 1 | perl -lane 'BEGIN{$|=1} print "@F[0,1]"' | LC_ALL=C ttyplot -A -2 -f -t "procs in R and D" +``` + + Binary files old/ttyplot-1.7.5/braille.png and new/ttyplot-1.7.6/braille.png differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ttyplot-1.7.5/snap/snapcraft.yaml new/ttyplot-1.7.6/snap/snapcraft.yaml --- old/ttyplot-1.7.5/snap/snapcraft.yaml 2026-06-22 04:17:25.000000000 +0200 +++ new/ttyplot-1.7.6/snap/snapcraft.yaml 2026-07-14 05:38:07.000000000 +0200 @@ -1,5 +1,5 @@ name: ttyplot -version: 1.6.2 +version: '1.7.6' summary: realtime plotting utility for terminal/console with data input from stdin description: | takes data from standard input / unix pipeline, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ttyplot-1.7.5/stresstest.c new/ttyplot-1.7.6/stresstest.c --- old/ttyplot-1.7.5/stresstest.c 2026-06-22 04:17:25.000000000 +0200 +++ new/ttyplot-1.7.6/stresstest.c 2026-07-14 05:38:07.000000000 +0200 @@ -20,18 +20,44 @@ const char help[] = "Usage:\n" - " stresstest [-2] [-c] [-g] [-n] [-r rate]\n" + " stresstest [-2] [-c] [-g] [-m] [-n] [-R] [-r rate]\n" " stresstest -h\n" "\n" " -h print this help message and exit\n" " -2 output two waves\n" " -c randomly chunk the output\n" " -g occasionally output garbage\n" + " -m output realistic metrics (CPU/bandwidth-like, random spikes)\n" " -n output negative values\n" + " -R output uniformly random values across the full range\n" " -r rate sample rate in samples/s (default: 100)\n" " -s seed set random seed\n"; -const char optstring[] = "h2cgnr:s:"; +const char optstring[] = "h2cgmnRr:s:"; + +// Return a uniformly random value in [lo, hi]. +static double rand_range(double lo, double hi) { + return lo + ((double)rand() / RAND_MAX) * (hi - lo); +} + +// Generate one realistic metrics sample. Models a mean-reverting baseline +// (Ornstein-Uhlenbeck-style random walk) with occasional sharp spikes that +// rise instantly and decay exponentially -- resembling CPU/bandwidth load. +// State is carried in *level and *spike so multiple series stay independent. +static double metric_sample(double *level, double *spike) { + const double target = 20.0; // baseline the walk reverts toward + const double theta = 0.02; // reversion strength + const double sigma = 1.5; // baseline jitter amplitude + const double decay = 0.85; // spike decay per sample (~6 samples to 1/e) + + double noise = ((double)rand() / RAND_MAX) * 2.0 - 1.0; // -1..1 + *level += theta * (target - *level) + sigma * noise; + *spike *= decay; + if (rand() < RAND_MAX / 100) // ~1% chance of a new spike + *spike += 30.0 + (rand() % 50); // magnitude 30..79 + double value = *level + *spike; + return value < 0 ? 0 : value; // load is never negative +} int main(int argc, char *argv[]) { char buffer[1024]; @@ -40,6 +66,8 @@ bool two_waves = false; bool chunked = false; bool add_garbage = false; + bool metrics = false; + bool super_random = false; bool output_negative = false; double rate = 100; unsigned int seed = time(NULL); @@ -59,9 +87,15 @@ case 'g': add_garbage = true; break; + case 'm': + metrics = true; + break; case 'n': output_negative = true; break; + case 'R': + super_random = true; + break; case 'r': rate = atof(optarg); break; @@ -81,15 +115,31 @@ const useconds_t delay = 1e6 / rate; srand(seed); + double level1 = 20.0, spike1 = 0.0; // metrics state, series 1 + double level2 = 20.0, spike2 = 0.0; // metrics state, series 2 + const double rmin = output_negative ? -100.0 : 0.0; // -R range + const double rmax = 100.0; + for (unsigned int n = 0;; n += 5) { - buffer_pos += sprintf(buffer + buffer_pos, "%.1f\n", - (sin(n * M_PI / 180) * 5) + (output_negative ? 0 : 5)); + double wave1; + if (metrics) + wave1 = metric_sample(&level1, &spike1); + else if (super_random) + wave1 = rand_range(rmin, rmax); + else + wave1 = (sin(n * M_PI / 180) * 5) + (output_negative ? 0 : 5); + buffer_pos += sprintf(buffer + buffer_pos, "%.1f\n", wave1); if (add_garbage && rand() <= RAND_MAX / 5) buffer_pos += sprintf(buffer + buffer_pos, "garbage "); if (two_waves) { - buffer_pos += - sprintf(buffer + buffer_pos, "%.1f\n", - (cos(n * M_PI / 180) * 5) + (output_negative ? 0 : 5)); + double wave2; + if (metrics) + wave2 = metric_sample(&level2, &spike2); + else if (super_random) + wave2 = rand_range(rmin, rmax); + else + wave2 = (cos(n * M_PI / 180) * 5) + (output_negative ? 0 : 5); + buffer_pos += sprintf(buffer + buffer_pos, "%.1f\n", wave2); if (add_garbage && rand() <= RAND_MAX / 5) buffer_pos += sprintf(buffer + buffer_pos, "garbage "); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ttyplot-1.7.5/ttyplot.1 new/ttyplot-1.7.6/ttyplot.1 --- old/ttyplot-1.7.5/ttyplot.1 2026-06-22 04:17:25.000000000 +0200 +++ new/ttyplot-1.7.6/ttyplot.1 2026-07-14 05:38:07.000000000 +0200 @@ -1,4 +1,4 @@ -.Dd January 20, 2024 +.Dd June 23, 2026 .Dt TTYPLOT 1 .Os .Sh NAME @@ -6,11 +6,21 @@ .Nd realtime terminal plotting utility .Sh SYNOPSIS .Nm -.Op Ar options +.Op Fl 2bBfr +.Op Fl c Ar plotchar +.Op Fl e Ar errcharmax +.Op Fl E Ar errcharmin +.Op Fl s Ar softmax +.Op Fl S Ar softmin +.Op Fl m Ar hardmax +.Op Fl M Ar hardmin +.Op Fl t Ar title +.Op Fl u Ar unit +.Op Fl C Ar colorspec .Nm --v +.Fl v .Nm --h +.Fl h .Sh DESCRIPTION .Nm takes data from standard input, @@ -25,12 +35,43 @@ and plots in text mode on a terminal in real time. .Pp Supports rate calculation for counters and up to two graphs -on a single display using reverse video for second line. +on a single display, using reverse video for the second line +or, experimentally, braille or block element line drawing in color. .Pp The following options are supported: .Bl -tag -width Ds .It Fl 2 Read two values and draw two plots, the second in reverse video. +.It Fl b +.Pq Experimental +Use braille line drawing instead of block/reverse-video bars. +With +.Fl 2 , +the two lines are distinguished by color rather than reverse video. +Requires a UTF-8 locale and a capable font, otherwise falls back. +.It Fl B +.Pq Experimental +Use block element (quadrant) line drawing, like +.Fl b . +.It Fl f +.Pq Experimental +Fill the area under the line in braille or block mode +.Pq line 1 . +.It Fl A +.Pq Experimental +Render the plot as +.Xr aalib 3 +ASCII art: smooth lines in plain 7-bit ASCII, for dumb terminals that cannot +display braille or block glyphs. +Like braille or block mode it draws up to two lines distinguished by color, and +.Fl f +fills the area under line 1. +aalib emits its native 7-bit ramp in a C locale; in a UTF-8 locale it is forced +to 8-bit glyphs which are folded back to ASCII, so for the smoothest output run +with +.Ev LC_ALL Ns = Ns Cm C . +Available only when compiled with aalib support +.Pq Cm make Ar AA Ns = Ns Ar 1 . .It Fl r Calculate counter rate and divide by measured sample interval. .It Fl c Ar plotchar @@ -55,6 +96,10 @@ Use .Ar softmax as the initial maximum value but allow it to grow with input. +.It Fl S Ar softmin +Use +.Ar softmin +as the initial minimum value but allow it to shrink with input. .It Fl m Ar hardmax Use .Ar hardmax @@ -78,6 +123,56 @@ .It Fl u Ar unit Label the vertical axis .Ar unit . +.It Fl C Ar colorspec +Set colors for plot elements. +.Ar colorspec +is either a predefined scheme name or a comma-separated list of +color numbers +.Pq Ar 0-7 : +.Ar plotchar Ns Op / Ns Ar line2 , +.Ar axes , +.Ar text , +.Ar title , +.Ar max_err , +.Ar min_err . +Only the first value, the plot line color, is required; the rest +are optional. +The optional +.Ar line2 +sets the second line color in braille or block mode. +For example +.Ql Fl C Ar 1,2,3,4,5,6 , +.Ql Fl C Ar 1,2 +or +.Ql Fl C Ar 1 . +The color numbers are +.Ar 0 +black, +.Ar 1 +red, +.Ar 2 +green, +.Ar 3 +yellow, +.Ar 4 +blue, +.Ar 5 +magenta, +.Ar 6 +cyan and +.Ar 7 +white. +Alternatively, +.Ar colorspec +may name a predefined scheme: +.Ar dark1 +or +.Ar dark2 +for dark terminals, +.Ar light1 +or +.Ar light2 +for light terminals. .It Fl v Print the current version and exit. .It Fl h @@ -92,6 +187,13 @@ Quit. .It Ic r Toggle "rate mode" on and off. +.It Ic Ctrl-L +Full screen redraw. +.El +.Pp +These commands do not work when standard input is a terminal; +in that case quit with +.Ql Ctrl-C . .Sh EXAMPLES CPU usage from .Xr vmstat 8 @@ -149,6 +251,23 @@ | ttyplot -t "ping to 8.8.8.8" -u ms .Ed .Pp +The same ping plot, drawn with braille +.Pq see Fl b : +.Bd -literal -offset indent +ping 8.8.8.8 \\ + | sed -u 's/^.*time=//g; s/ ms//g' \\ + | ttyplot -b -t "ping to 8.8.8.8" -u ms +.Ed +.Pp +The same ping plot, with custom colors for the line, axes, text +and title +.Pq see Fl C : +.Bd -literal -offset indent +ping 8.8.8.8 \\ + | sed -u 's/^.*time=//g; s/ ms//g' \\ + | ttyplot -t "ping to 8.8.8.8" -u ms -C 2,3,4,5 +.Ed +.Pp WiFi signal level in -dBM (higher is worse) using .Xr iwconfig 8 : .Bd -literal -offset indent @@ -310,19 +429,21 @@ Its readme was converted into this manual page by .An Sijmen J. Mulder Aq Mt [email protected] . .Sh BUGS -By default in standard in- and output are is buffered. +By default standard input and output are buffered. This can be worked around in various .Lk http://www.perkin.org.uk/posts/how-to-fix-stdio-buffering.html ways . .Pp +When there is no more data, .Nm -quits and erases the screen when there is no more data. -This is by design and can be worked around by adding -.Xr sleep 1 -or -.Xr read 1 , -for example: +prints +.Dq input stream closed +and waits rather than quitting +.Pq prior to version 1.5 it quit . +The screen is normally erased on exit because of the terminal's +.Lk https://invisible-island.net/xterm/xterm.faq.html#xterm_tite "alternate screen" ; +this can be worked around, for example: .Pp -.Dl { echo "1 2 3"; sleep 1000; } | ttyplot +.Dl echo 1 2 3 | TERM=vt100 ttyplot .Pp When running interactively and non-numeric data is entered .Pq e.g. some key diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ttyplot-1.7.5/ttyplot.c new/ttyplot-1.7.6/ttyplot.c --- old/ttyplot-1.7.5/ttyplot.c 2026-06-22 04:17:25.000000000 +0200 +++ new/ttyplot-1.7.6/ttyplot.c 2026-07-14 05:38:07.000000000 +0200 @@ -41,6 +41,14 @@ #include <err.h> #endif +// Experimental, opt-in ASCII-art rendering backend (build with -DAALIB -laa). +#ifdef AALIB +#include <aalib.h> +#define AA_OPT "A" +#else +#define AA_OPT "" +#endif + #define T_RARR '>' #define T_UARR '^' #ifdef NOACS @@ -71,6 +79,9 @@ #define C_CYAN 6 #define C_WHITE 7 +#define PAIR_BR1 8 +#define PAIR_BR2 9 + // Define color element indices enum ColorElement { LINE_COLOR = 0, @@ -101,10 +112,15 @@ static int width = 0, height = 0, n = -1, v = 0, c = 0, rate = 0, two = 0, plotwidth = WIDTH_MIN - WIDTH_MARGIN, plotheight = 0; static bool fake_clock = false; +static int braille = 0; +static int braille_fill = 0; +static int block = 0; +static int aa = 0; // only settable when compiled with -DAALIB (see optstring) static char *errstr = NULL; static bool redraw_needed = false; // Array of colors for different elements, -1 means no color specified static int colors[NUM_COLOR_ELEMENTS] = {-1, -1, -1, -1, -1, -1}; +static int line2color = -1; static const char *verstring = "https://github.com/tenox7/ttyplot " VERSION_STR; static void usage(void) { @@ -116,6 +132,13 @@ " ttyplot -v\n" "\n" " -2 read two values and draw two plots, the second one is in reverse video\n" + " -b braille line drawing mode (two lines distinguished by color)\n" + " -B block elements drawing mode (quadrants), like -b\n" + " -f fill area under the line in braille/block mode (line 1)\n" +#ifdef AALIB + " -A (experimental) aalib ASCII-art line mode, two lines in color (-f fills " + "line 1)\n" +#endif " -r rate of a counter (divide value by measured sample interval)\n" " -c character to use for plot line, eg @ # %% . etc\n" " -e character to use for error line when value exceeds hardmax (default: e)\n" @@ -129,7 +152,7 @@ "lower-limit of the plot scale is fixed\n" " -t title of the plot\n" " -u unit displayed beside vertical bar\n" - " -C color[,axes,text,title,max_err,min_err] set colors (0-7) for elements:\n" + " -C color[/line2][,axes,text,title,max_err,min_err] set colors (0-7):\n" " First value: plot line color\n" " Second value: axes color (optional)\n" " Third value: text color (optional)\n" @@ -138,8 +161,8 @@ " Sixth value: min error indicator color (optional)\n" " Example: -C 1,2,3,4,5,6 or -C 1,2 or -C 1\n" " Predefined color schemes:\n" - " -C dark1 Blue-cyan-yellow scheme for dark terminals\n" - " -C dark2 Purple-yellow-green scheme for dark terminals\n" + " -C dark1 Red-green lines for dark terminals\n" + " -C dark2 Blue-yellow lines for dark terminals\n" " -C light1 Green-blue-red scheme for light terminals\n" " -C light2 Blue-green-yellow scheme for light terminals\n" " -v print the current version and exit\n" @@ -158,21 +181,23 @@ // Set a predefined color scheme static void set_color_scheme(const char *scheme_name) { if (strcmp(scheme_name, "dark1") == 0) { - // Blue-cyan-yellow scheme for dark terminals - colors[LINE_COLOR] = C_BLUE; // Blue for plot line + // Red-green lines for dark terminals + colors[LINE_COLOR] = C_RED; // Red for plot line + line2color = C_GREEN; // Green for second plot line colors[AXES_COLOR] = C_CYAN; // Cyan for axes colors[TEXT_COLOR] = C_WHITE; // White for text colors[TITLE_COLOR] = C_YELLOW; // Yellow for title colors[MAX_ERROR_COLOR] = C_RED; // Red for max error colors[MIN_ERROR_COLOR] = C_GREEN; // Green for min error } else if (strcmp(scheme_name, "dark2") == 0) { - // Purple-yellow-green scheme for dark terminals - colors[LINE_COLOR] = C_MAGENTA; // Magenta for plot line - colors[AXES_COLOR] = C_YELLOW; // Yellow for axes - colors[TEXT_COLOR] = C_CYAN; // Cyan for text - colors[TITLE_COLOR] = C_GREEN; // Green for title - colors[MAX_ERROR_COLOR] = C_RED; // Red for max error - colors[MIN_ERROR_COLOR] = C_BLUE; // Blue for min error + // Blue-yellow lines for dark terminals + colors[LINE_COLOR] = C_BLUE; // Blue for plot line + line2color = C_YELLOW; // Yellow for second plot line + colors[AXES_COLOR] = C_CYAN; // Cyan for axes + colors[TEXT_COLOR] = C_WHITE; // White for text + colors[TITLE_COLOR] = C_GREEN; // Green for title + colors[MAX_ERROR_COLOR] = C_RED; // Red for max error + colors[MIN_ERROR_COLOR] = C_MAGENTA; // Magenta for min error } else if (strcmp(scheme_name, "light1") == 0) { // Green-blue-red scheme for light terminals colors[LINE_COLOR] = C_GREEN; // Green for plot line @@ -477,6 +502,209 @@ attroff(COLOR_PAIR(LINE_COLOR + 1)); } +// braille (2x4) bits indexed [(y%4)*2 + (x&1)]; quadrant (2x2) bits indexed [(y%2)*2 + +// (x&1)] +static const unsigned char braille_bits[8] = {0x01, 0x08, 0x02, 0x10, + 0x04, 0x20, 0x40, 0x80}; +static const unsigned char quad_bits[4] = {1, 2, 4, 8}; +static const wchar_t quad_glyphs[16] = {L' ', 0x2598, 0x259D, 0x2580, 0x2596, 0x258C, + 0x259E, 0x259B, 0x2597, 0x259A, 0x2590, 0x259C, + 0x2584, 0x2599, 0x259F, 0x2588}; + +// Render v1/v2 onto a sub-cell pixel grid (sub vertical pixels per cell, 2 horizontal). +// glyphs==NULL selects braille (U+2800+bits); otherwise a 16-entry quadrant table. +static void plot_dots(int ph, int pw, double *v1, double *v2, double max, double min, + int n, int sub, const unsigned char *bits, + const wchar_t *glyphs) { + const int first_col = 3; + const int dh = ph * sub, dw = pw * 2; + + if (ph <= 0 || pw <= 0) + return; + + double range = max - min; + if (range <= 0) + range = 1; + + unsigned char *canvas = calloc((size_t)ph * pw, 1); + unsigned char *owner = calloc((size_t)ph * pw, 1); + if (! canvas || ! owner) { + free(canvas); + free(owner); + return; + } + + for (int pass = 0; pass < 2; pass++) { + double *vals = (pass == 0) ? v1 : v2; + unsigned char who = (pass == 0) ? 1 : 2; + int do_fill = (pass == 0) ? braille_fill : 0; + if (! vals) + continue; + + int prev_y = 0; + bool prev_valid = false; + for (int x = 0; x < dw; x++) { + int a = x / 2; + double va = vals[(n + 1 + a) % pw]; + if (isnan(va)) { + prev_valid = false; + continue; + } + double v = va; + if ((x & 1) && a + 1 < pw) { + double vb = vals[(n + 1 + a + 1) % pw]; + if (! isnan(vb)) + v = (va + vb) / 2; + } + double frac = (v - min) / range; + if (frac < 0) + frac = 0; + if (frac > 1) + frac = 1; + int y = (dh - 1) - (int)lrint(frac * (dh - 1)); + + int lo = y, hi = y; + if (do_fill) { + hi = dh - 1; + } else if (prev_valid) { + lo = (prev_y < y) ? prev_y : y; + hi = (prev_y < y) ? y : prev_y; + } + for (int yy = lo; yy <= hi; yy++) { + int idx = (yy / sub) * pw + a; + canvas[idx] |= bits[(yy % sub) * 2 + (x & 1)]; + owner[idx] = who; + } + prev_y = y; + prev_valid = true; + } + } + + for (int r = 0; r < ph; r++) { + for (int c = 0; c < pw; c++) { + unsigned char b = canvas[r * pw + c]; + if (! b) + continue; + wchar_t ws[2] = {glyphs ? glyphs[b] : (wchar_t)(0x2800 + b), 0}; + cchar_t cc; + short pair = (owner[r * pw + c] == 2) ? PAIR_BR2 : PAIR_BR1; + setcchar(&cc, ws, A_NORMAL, pair, NULL); + mvadd_wch(1 + r, first_col + c, &cc); + } + } + + free(canvas); + free(owner); +} + +#ifdef AALIB +// aa is the "dumb terminal" tier: smooth lines in plain 7-bit ASCII. In a C/POSIX +// locale aalib natively emits its 7-bit glyph ramp, which we pass through untouched. +// In a UTF-8 locale it insists on 8-bit CP437 glyphs instead (a load-time decision we +// can't override in-process), so we fold those high bytes down to a 7-bit ASCII +// approximation by shape. For the authentic smooth ramp, run with LC_ALL=C. +static char aa_ascii(unsigned char b) { + if (b >= 0x20 && b < 0x7f) // includes aalib's own 7-bit ramp + return (char)b; + switch (b) { + case 0xDB: // full block + case 0xB2: + return '#'; // dark shade + case 0xB1: + return '+'; // medium shade + case 0xB0: + return ':'; // light shade + case 0xDF: + return '"'; // upper half + case 0xDC: + return '.'; // lower half + case 0xDD: + return '['; // left half + case 0xDE: + return ']'; // right half + case 0xCC: // diagonal quadrants + case 0xA5: + return 'x'; + } + return b ? '*' : ' '; +} + +// Experimental: render up to two series as aalib 7-bit ASCII-art, one pass each so the +// two lines can be colored independently (like braille/block mode: line 1 PAIR_BR1, +// line 2 PAIR_BR2). Without -f each series is a connected line; with -f, line 1's area +// is filled. The aalib context is recreated every paint so it tracks resizes for free. +static void plot_aa(int ph, int pw, double *v1, double *v2, double max, double min, + int n) { + const int first_col = 3; + if (ph <= 0 || pw <= 0) + return; + + double range = max - min; + if (range <= 0) + range = 1; + + struct aa_hardware_params hp = aa_defparams; + hp.width = pw; + hp.height = ph; + aa_context *c = aa_init(&mem_d, &hp, NULL); + if (! c) + return; + + const int iw = aa_imgwidth(c), ih = aa_imgheight(c); + + for (int pass = 0; pass < 2; pass++) { + double *vals = (pass == 0) ? v1 : v2; + int do_fill = (pass == 0) ? braille_fill : 0; // -f fills line 1 only + short pair = (pass == 0) ? PAIR_BR1 : PAIR_BR2; + if (! vals) + continue; + + memset(aa_image(c), 0, (size_t)iw * ih); + + int prev_y = 0; + bool prev_valid = false; + for (int x = 0; x < iw; x++) { + int a = x * pw / iw; // image column -> data column + double va = vals[(n + 1 + a) % pw]; + if (isnan(va)) { + prev_valid = false; + continue; + } + double frac = (va - min) / range; + if (frac < 0) + frac = 0; + if (frac > 1) + frac = 1; + int y = (ih - 1) - (int)lrint(frac * (ih - 1)); + + int lo = y, hi = y; + if (do_fill) { + hi = ih - 1; + } else if (prev_valid) { + lo = (prev_y < y) ? prev_y : y; + hi = (prev_y < y) ? y : prev_y; + } + for (int yy = lo; yy <= hi; yy++) + aa_putpixel(c, x, yy, 255); + prev_y = y; + prev_valid = true; + } + + aa_render(c, &aa_defrenderparams, 0, 0, pw, ph); + unsigned char *text = aa_text(c); + for (int r = 0; r < ph; r++) + for (int col = 0; col < pw; col++) { + unsigned char b = text[r * pw + col]; + if (b == 0 || b == ' ') + continue; + mvaddch(1 + r, first_col + col, (chtype)aa_ascii(b) | COLOR_PAIR(pair)); + } + } + + aa_close(c); +} +#endif + static void show_all_centered(const char *message) { const size_t message_len = strlen(message); const int x = ((int)message_len > width) ? 0 : (width / 2 - (int)message_len / 2); @@ -554,7 +782,16 @@ if (colors[TEXT_COLOR] != -1) attron(COLOR_PAIR(TEXT_COLOR + 1)); - mvvline_set(height - 2, 5, &plotchar, 1); + if (braille || block) { + wchar_t iw[2] = {braille ? 0x28FF : 0x2588, 0}; + cchar_t ind; + setcchar(&ind, iw, A_NORMAL, PAIR_BR1, NULL); + mvadd_wch(height - 2, 5, &ind); + } else if (aa) { + mvaddch(height - 2, 5, '#' | COLOR_PAIR(PAIR_BR1)); + } else { + mvvline_set(height - 2, 5, &plotchar, 1); + } if (v > 0) { mvprintw(height - 2, 7, "last=%.1f min=%.1f max=%.1f avg=%.1f %s ", values1[n], min1, max1, avg1, unit); @@ -562,7 +799,16 @@ printw(" interval=%.3gs", td); } if (two) { - mvaddch(height - 1, 5, ' ' | A_REVERSE); + if (braille || block) { + wchar_t iw[2] = {braille ? 0x28FF : 0x2588, 0}; + cchar_t ind; + setcchar(&ind, iw, A_NORMAL, PAIR_BR2, NULL); + mvadd_wch(height - 1, 5, &ind); + } else if (aa) { + mvaddch(height - 1, 5, '#' | COLOR_PAIR(PAIR_BR2)); + } else { + mvaddch(height - 1, 5, ' ' | A_REVERSE); + } if (v > 0) { mvprintw(height - 1, 7, "last=%.1f min=%.1f max=%.1f avg=%.1f %s ", values2[n], min2, max2, avg2, unit); @@ -572,8 +818,19 @@ if (colors[TEXT_COLOR] != -1) attroff(COLOR_PAIR(TEXT_COLOR + 1)); - plot_values(plotheight, plotwidth, values1, two ? values2 : NULL, max, min, n, - &plotchar, &max_errchar, &min_errchar, hardmax, hardmin); + if (braille) + plot_dots(plotheight, plotwidth, values1, two ? values2 : NULL, max, min, n, 4, + braille_bits, NULL); + else if (block) + plot_dots(plotheight, plotwidth, values1, two ? values2 : NULL, max, min, n, 2, + quad_bits, quad_glyphs); +#ifdef AALIB + else if (aa) + plot_aa(plotheight, plotwidth, values1, two ? values2 : NULL, max, min, n); +#endif + else + plot_values(plotheight, plotwidth, values1, two ? values2 : NULL, max, min, n, + &plotchar, &max_errchar, &min_errchar, hardmax, hardmin); draw_axes(height, plotheight, plotwidth, max, min, unit); @@ -819,7 +1076,7 @@ int i; bool stdin_is_open = true; int cached_opterr; - const char *optstring = "2rc:e:E:s:S:m:M:t:u:vhC:"; + const char *optstring = "2bBf" AA_OPT "rc:e:E:s:S:m:M:t:u:vhC:"; int show_ver; int show_usage; @@ -892,6 +1149,20 @@ case '2': two = 1; break; + case 'b': + braille = 1; + break; + case 'B': + block = 1; + break; +#ifdef AALIB + case 'A': + aa = 1; + break; +#endif + case 'f': + braille_fill = 1; + break; case 'c': mbtowc(&plotchar.chars[0], optarg, MB_CUR_MAX); break; @@ -913,7 +1184,16 @@ int color_idx = 0; while (token != NULL && color_idx < NUM_COLOR_ELEMENTS) { - colors[color_idx++] = atoi(token); + if (color_idx == 0) { + char *slash = strchr(token, '/'); + if (slash) { + *slash = '\0'; + line2color = atoi(slash + 1); + } + } + if (*token) + colors[color_idx] = atoi(token); + color_idx++; token = strtok(NULL, ","); } @@ -949,6 +1229,10 @@ if (hardmax <= hardmin) hardmax = FLT_MAX; + // braille/block need wide glyphs; aa is 7-bit ASCII so it works on dumb terminals. + if (MB_CUR_MAX <= 1) + braille = block = 0; + if (initscr() == NULL) { fprintf(stderr, "Error: failed to initialize ncurses\n"); exit(1); @@ -968,7 +1252,7 @@ } } - if (has_colors) { + if (has_colors || braille || block || aa) { start_color(); use_default_colors(); @@ -986,6 +1270,15 @@ init_pair(i + 1, colors[i], -1); // -1 for default background } } + + if (braille || block || aa) { + int br1 = (colors[LINE_COLOR] != -1) ? colors[LINE_COLOR] : C_GREEN; + int br2 = (line2color != -1) ? line2color + : (br1 == C_BLUE) ? C_GREEN + : C_BLUE; + init_pair(PAIR_BR1, br1, -1); + init_pair(PAIR_BR2, br2, -1); + } } gettimeofday(&now, NULL);
