ttyplot

Realtime terminal plotter
git clone git://git.sgregoratto.me/ttyplot
Log | Files | Refs | README | LICENSE

ttyplot.c (8593B)


      1 //
      2 // ttyplot: a realtime plotting utility for terminal with data input from stdin
      3 // Copyright (c) 2018 by Antoni Sawicki
      4 // Copyright (c) 2019 by Google LLC
      5 // Apache License 2.0
      6 //
      7 
      8 #include <stdio.h>
      9 #include <string.h>
     10 #include <stdlib.h>
     11 #include <unistd.h>
     12 #include <float.h>
     13 #include <time.h>
     14 #include <curses.h>
     15 #include <signal.h>
     16 
     17 #ifdef __OpenBSD__
     18 #include <err.h>
     19 #endif
     20 
     21 #define verstring "github.com/tenox7/ttyplot 1.4"
     22 
     23 #ifdef NOACS
     24 #define T_HLINE '-'
     25 #define T_VLINE '|'
     26 #define T_RARR '>'
     27 #define T_UARR '^'
     28 #define T_LLCR 'L'
     29 #else
     30 #define T_HLINE ACS_HLINE
     31 #define T_VLINE ACS_VLINE
     32 #define T_RARR ACS_RARROW
     33 #define T_UARR ACS_UARROW
     34 #define T_LLCR ACS_LLCORNER
     35 #endif
     36 
     37 void usage() {
     38     printf("Usage:\n  ttyplot [-2] [-r] [-c plotchar] [-s scale] [-m max] [-t title] [-u unit]\n\n"
     39             "  -2 read two values and draw two plots, the second one is in reverse video\n"
     40             "  -r rate of a counter (divide value by measured sample interval)\n"
     41             "  -c character to use for plot line, eg @ # %% . etc\n"
     42             "  -e character to use for plot error line when value exceeds hardmax (default: e)\n"
     43             "  -s minimum/initial scale of the plot (can go above if data input has larger value)\n"
     44             "  -m maximum value, if exceeded draws error line (see -e), plot scale is fixed\n"
     45             "  -t title of the plot\n"
     46             "  -u unit displayed beside vertical bar\n\n");
     47     exit(0);
     48 }
     49 
     50 void getminmax(int pw, int n, double *values, double *min, double *max, double *avg) {
     51     double tot=0;
     52     int i=0;
     53 
     54     *min=FLT_MAX;
     55     *max=FLT_MIN;
     56     tot=FLT_MIN;
     57 
     58     for(i=0; i<pw; i++) {
     59        if(values[i]>*max)
     60             *max=values[i];
     61 
     62         if(values[i]<*min)
     63             *min=values[i];
     64 
     65         tot=tot+values[i];
     66     }
     67 
     68     *avg=tot/pw;
     69 }
     70 
     71 void draw_axes(int h, int w, int ph, int pw, double max, char *unit) {
     72     mvhline(h-3, 2, T_HLINE, pw);
     73     mvvline(2, 2, T_VLINE, ph);
     74     mvprintw(1, 4, "%.1f %s", max, unit);
     75     mvprintw((ph/4)+1, 4, "%.1f %s", max*3/4, unit);
     76     mvprintw((ph/2)+1, 4, "%.1f %s", max/2, unit);
     77     mvprintw((ph*3/4)+1, 4, "%.1f %s", max/4, unit);
     78     mvaddch(h-3, 2+pw, T_RARR);
     79     mvaddch(1, 2, T_UARR);
     80     mvaddch(h-3, 2, T_LLCR);
     81 }
     82 
     83 void draw_line(int x, int ph, int l1, int l2, chtype c1, chtype c2, chtype ce) {
     84     if(l1 > l2) {
     85         mvvline(ph+1-l1, x, c1, l1-l2 );
     86         mvvline(ph+1-l2, x, c2|A_REVERSE, l2 );
     87     } else if(l1 < l2) {
     88         mvvline(ph+1-l2, x, (c2==ce) ? c2|A_REVERSE : ' '|A_REVERSE,  l2-l1 );
     89         mvvline(ph+1-l1, x, c1|A_REVERSE, l1 );
     90     } else {
     91         mvvline(ph+1-l2, x, c2|A_REVERSE, l2 );
     92     }
     93 }
     94 
     95 void plot_values(int h, int w, int ph, int pw, double *v1, double *v2, double max, int n, chtype pc, chtype ce, double hm) {
     96     int i;
     97     int x=3;
     98 
     99     for(i=n+1; i<pw; i++)
    100         draw_line(x++, ph,
    101                   (v1[i]>hm) ? ph : (int)((v1[i]/max)*(double)ph),
    102                   (v2[i]>hm) ? ph : (int)((v2[i]/max)*(double)ph),
    103                   (v1[i]>hm) ? ce : pc,
    104                   (v2[i]>hm) ? ce : pc,
    105                   ce);
    106 
    107     for(i=0; i<=n; i++)
    108         draw_line(x++, ph,
    109                   (v1[i]>hm) ? ph : (int)((v1[i]/max)*(double)ph),
    110                   (v2[i]>hm) ? ph : (int)((v2[i]/max)*(double)ph),
    111                   (v1[i]>hm) ? ce : pc,
    112                   (v2[i]>hm) ? ce : pc,
    113                   ce);
    114 }
    115 
    116 void resize(int sig) {
    117     endwin();
    118     refresh();
    119 }
    120 
    121 void finish(int sig) {
    122     curs_set(FALSE);
    123     echo();
    124     refresh();
    125     endwin();
    126     exit(0);
    127 }
    128 
    129 int main(int argc, char *argv[]) {
    130     double values1[1024]={0};
    131     double values2[1024]={0};
    132     double cval1=FLT_MAX, pval1=FLT_MAX;
    133     double cval2=FLT_MAX, pval2=FLT_MAX;
    134     double min1=FLT_MAX, max1=FLT_MIN, avg1=0;
    135     double min2=FLT_MAX, max2=FLT_MIN, avg2=0;
    136     int n=0;
    137     int r=0;
    138     int width=0, height=0;
    139     int plotwidth=0, plotheight=0;
    140     time_t t1,t2,td;
    141     struct tm *lt;
    142     int c;
    143     chtype plotchar=T_VLINE, errchar='e';
    144     double max=FLT_MIN;
    145     double softmax=FLT_MIN;
    146     double hardmax=FLT_MAX;
    147     char title[256]=".: ttyplot :.";
    148     char unit[64]={0};
    149     char ls[256]={0};
    150     int rate=0;
    151     int two=0;
    152 
    153     opterr=0;
    154     while((c=getopt(argc, argv, "2rc:c:e:s:m:t:u:")) != -1)
    155         switch(c) {
    156             case 'r':
    157                 rate=1;
    158                 break;
    159             case '2':
    160                 two=1;
    161                 plotchar='|';
    162                 break;
    163             case 'c':
    164                 plotchar=optarg[0];
    165                 break;
    166             case 'e':
    167                 errchar=optarg[0];
    168                 break;
    169             case 's':
    170                 softmax=atof(optarg);
    171                 break;
    172             case 'm':
    173                 hardmax=atof(optarg);
    174                 break;
    175             case 't':
    176                 snprintf(title, sizeof(title), "%s", optarg);
    177                 break;
    178             case 'u':
    179                 snprintf(unit, sizeof(unit), "%s", optarg);
    180                 break;
    181             case '?':
    182                 usage();
    183                 break;
    184         }
    185 
    186     initscr(); /* uses filesystem, so before pledge */
    187 
    188     #ifdef __OpenBSD__
    189     if (pledge("stdio tty", NULL) == -1)
    190         err(1, "pledge");
    191     #endif
    192 
    193     time(&t1);
    194     noecho();
    195     curs_set(FALSE);
    196     signal(SIGWINCH, (void*)resize);
    197     signal(SIGINT, (void*)finish);
    198 
    199     erase();
    200     refresh();
    201     #ifdef NOGETMAXYX
    202     height=LINES;
    203     width=COLS;
    204     #else
    205     getmaxyx(stdscr, height, width);
    206     #endif
    207     mvprintw(height/2, (width/2)-14, "waiting for data from stdin");
    208     refresh();
    209 
    210     while(1) {
    211         if(two)
    212             r=scanf("%lf %lf", &values1[n], &values2[n]);
    213         else
    214             r=scanf("%lf", &values1[n]);
    215         if(r==0) {
    216             while(getchar()!='\n');
    217             continue;
    218         }
    219         else if(r<0) {
    220             break;
    221         }
    222 
    223         if(values1[n] < 0)
    224             values1[n] = 0;
    225         if(values2[n] < 0)
    226             values2[n] = 0;
    227 
    228         if(rate) {
    229             t2=t1;
    230             time(&t1);
    231             td=t1-t2;
    232             if(td==0)
    233                 td=1;
    234 
    235             if(cval1==FLT_MAX)
    236                 pval1=values1[n];
    237             else
    238                 pval1=cval1;
    239             cval1=values1[n];
    240 
    241             values1[n]=(cval1-pval1)/td;
    242 
    243             if(values1[n] < 0) // counter rewind
    244                 values1[n]=0;
    245 
    246             if(two) {
    247                 if(cval2==FLT_MAX)
    248                     pval2=values2[n];
    249                 else
    250                     pval2=cval2;
    251                 cval2=values2[n];
    252 
    253                 values2[n]=(cval2-pval2)/td;
    254 
    255                 if(values2[n] < 0) // counter rewind
    256                     values2[n]=0;
    257             }
    258         } else {
    259             time(&t1);
    260         }
    261 
    262         erase();
    263         #ifdef _AIX
    264         refresh();
    265         #endif
    266         #ifdef NOGETMAXYX
    267         height=LINES;
    268         width=COLS;
    269         #else
    270         getmaxyx(stdscr, height, width);
    271         #endif
    272         plotheight=height-4;
    273         plotwidth=width-4;
    274         if(plotwidth>=(sizeof(values1)/sizeof(double))-1)
    275             return 0;
    276 
    277         getminmax(plotwidth, n, values1, &min1, &max1, &avg1);
    278         getminmax(plotwidth, n, values2, &min2, &max2, &avg2);
    279 
    280         if(max1>max2)
    281             max=max1;
    282         else
    283             max=max2;
    284 
    285         if(max<softmax)
    286             max=softmax;
    287         if(hardmax!=FLT_MAX)
    288             max=hardmax;
    289 
    290         mvprintw(height-1, width-sizeof(verstring)/sizeof(char), verstring);
    291 
    292         lt=localtime(&t1);
    293         #ifdef __sun
    294         asctime_r(lt, ls, sizeof(ls));
    295         #else
    296         asctime_r(lt, ls);
    297         #endif
    298         mvprintw(height-2, width-strlen(ls), "%s", ls);
    299 
    300         #ifdef _AIX
    301         mvaddch(height-2, 5, plotchar);
    302         #else
    303         mvvline(height-2, 5, plotchar|A_NORMAL, 1);
    304         #endif
    305         mvprintw(height-2, 7, "last=%.1f min=%.1f max=%.1f avg=%.1f %s ",  values1[n], min1, max1, avg1, unit);
    306         if(rate)
    307             printw(" interval=%ds", td);
    308 
    309         if(two) {
    310             mvaddch(height-1, 5, ' '|A_REVERSE);
    311             mvprintw(height-1, 7, "last=%.1f min=%.1f max=%.1f avg=%.1f %s   ",  values2[n], min2, max2, avg2, unit);
    312         }
    313 
    314         plot_values(height, width, plotheight, plotwidth, values1, values2, max, n, plotchar, errchar, hardmax);
    315 
    316         draw_axes(height, width, plotheight, plotwidth, max, unit);
    317 
    318         mvprintw(0, (width/2)-(strlen(title)/2), "%s", title);
    319 
    320         if(n<(plotwidth)-1)
    321             n++;
    322         else
    323             n=0;
    324 
    325         move(0,0);
    326         refresh();
    327     }
    328 
    329     endwin();
    330     return 0;
    331 }