increment minimum marker position
[barcat.git] / barcat
1 #!/usr/bin/perl -CA
2 use 5.014;
3 use warnings;
4 use utf8;
5 use List::Util qw( min max sum );
6 use open qw( :std :utf8 );
7 use re '/msx';
8
9 our $VERSION = '1.09';
10
11 my %opt;
12 if (@ARGV) {
13 require Getopt::Long;
14 Getopt::Long->import('2.33', qw( :config gnu_getopt ));
15 GetOptions(\%opt,
16         'ascii|a!',
17         'color|C!',
18         'M' => sub { $opt{color} = 0 },
19         'field|f=s' => sub {
20                 eval {
21                         local $_ = $_[1];
22                         s/\A[0-9]+\z/(?:\\S*\\h+){$_}\\K/;
23                         s{\A[+]([0-9]*)\z}{
24                                 (!!$1 && '(?:\d+\D+\b){'.$1.'}\K') . '\s* (?=\d)'
25                         }e;
26                         $opt{anchor} = qr/$_/;
27                 } or die $@ =~ s/(?:\ at\ \N+)?\Z/ for option $_[0]/r;
28         },
29         'count|c!',
30         'human-readable|H!',
31         'sexagesimal!',
32         'reformat!',
33         'interval|t:i',
34         'trim|length|l=s' => sub {
35                 my ($optname, $optval) = @_;
36                 $optval =~ s/%$// and $opt{trimpct}++;
37                 $optval =~ m/\A-?[0-9]+\z/ or die(
38                         "Value \"$optval\" invalid for option $optname",
39                         " (number or percentage expected)\n"
40                 );
41                 $opt{trim} = $optval;
42         },
43         'value-length=i',
44         'minval=f',
45         'maxval=f',
46         'limit|L:s' => sub {
47                 my ($optname, $optval) = @_;
48                 $optval ||= 0;
49                 $optval =~ /\A-[0-9]+\z/ and $optval .= '-';  # tail shorthand
50                 $optval =~ s/[+]/--/;
51                 my ($start, $end) =
52                 $optval =~ m/\A (?: (-? [0-9]+)? - )? (-? [0-9]+)? \z/ or die(
53                         "Value \"$optval\" invalid for option limit",
54                         " (range expected)\n"
55                 );
56                 $start ||= 1;
57                 $start--;
58                 s/\A-0*\z// and $_ ||= undef for $end // ();
59
60                 $opt{hidemin} = sub {
61                         my ($lines) = @_;
62                         if ($start < 0) {
63                                 return max(0, $lines + $start + 2);
64                         }
65                         return $start;
66                 } if $start;
67                 $opt{hidemax} = sub {
68                         my ($limit, $offset) = @_;
69                         if ($end < 0) {
70                                 return $offset - $end - 1; # count
71                         }
72                         elsif ($start < 0) {
73                                 return $limit - $end + 1; # bottom
74                         }
75                         elsif ($end <= $limit) {
76                                 return $end - 1; # less
77                         }
78                         return $limit;
79                 } if defined $end;
80         },
81         'log|e!',
82         'header!',
83         'markers|m=s',
84         'graph-format=s' => sub {
85                 $opt{'graph-format'} = substr $_[1], 0, 1;
86         },
87         'spark|_!',
88         'indicators:s',
89         'palette=s' => sub {
90                 $opt{palette} = {
91                         ''     => [],
92                         fire   => [qw( 90 31 91 33 93 97 96 )],
93                         fire256=> [map {"38;5;$_"} qw(
94                                 235  52 88 124 160 196
95                                 202 208 214 220 226  227 228 229 230 231  159
96                         )],
97                         whites => [qw( 1;30 0;37 1;37 )],
98                         grays  => [map {"38;5;$_"} 0, 232..255, 15],
99                         random => [map {"38;5;$_"} List::Util::shuffle(17..231)],
100                         rainbow=> [map {"38;5;$_"}
101                                 196, # r
102                                 (map { 196 + $_*6   } 0..4), # +g
103                                 (map { 226 - $_*6*6 } 0..4), # -r
104                                 (map {  46 + $_     } 0..4), # +b
105                                 (map {  51 - $_*6   } 0..4), # -g
106                                 (map {  21 + $_*6*6 } 0..4), # +r
107                                 (map { 201 - $_     } 0..4), # -b
108                                 196,
109                         ],
110                 }->{$_[1]} // do {
111                         my @vals = split /[^0-9;]/, $_[1]
112                                 or die "Empty palette resulting from \"$_[1]\"\n";
113                         \@vals;
114                 };
115         },
116         'stat|s!',
117         'report=s',
118         'signal-stat=s',
119         'unmodified|u!',
120         'width|w=i',
121         'version|V' => sub {
122                 my $mascot = $opt{ascii} ? '=^,^=' : 'ฅ^•ﻌ•^ฅ';
123                 say "barcat $mascot version $VERSION";
124                 exit;
125         },
126         'usage|h' => sub {
127                 /^=/ ? last : print for readline *DATA;  # text between __END__ and pod
128                 exit;
129         },
130         'help|?'  => sub {
131                 require Pod::Usage;
132                 Pod::Usage::pod2usage(
133                         -exitval => 0, -perldocopt => '-oman', -verbose => 2,
134                 );
135         },
136 ) or exit 64;  # EX_USAGE
137 }
138
139 $opt{width} ||= $ENV{COLUMNS} || qx(tput cols) || 80 unless $opt{spark};
140 $opt{color} //= $ENV{NO_COLOR} ? 0 : -t *STDOUT;  # enable on tty
141 $opt{'graph-format'} //= '-';
142 $opt{trim}   *= $opt{width} / 100 if $opt{trimpct};
143 $opt{units}   = [split //, ' kMGTPEZYRQqryzafpn'.($opt{ascii} ? 'u' : 'μ').'m']
144         if $opt{'human-readable'};
145 $opt{'value-length'} = 4 if $opt{units};
146 $opt{'value-length'} = 1 if $opt{unmodified};
147 $opt{'signal-stat'} //= exists $SIG{INFO} ? 'INFO' : 'QUIT';
148 $opt{markers} //= '=avg >31.73v <68.27v +50v |0';
149 $opt{report} //= join('',
150         '${partsum+; $_ .= " of "}',
151         '${sum+; color(1); $_ .= " total in "}',
152         '${count#} values',
153         '${lines#; $_ = $_ != @order && " over $_ lines"}',
154         sprintf('${count: (%s)}', join ', ',
155                 '${min; color(31)} min',
156                 '${avg; $opt{reformat} or $_ = sprintf "%0.2f", $_; color(36)} avg',
157                 '${max; color(32)} max',
158         ),
159 );
160 $opt{palette} //= $opt{color} && [31, 90, 32];
161 $opt{indicators} = [split //, $opt{indicators} ||
162         ($opt{ascii} ? ' .oO' : $opt{spark} ? ' ▁▂▃▄▅▆▇█' : ' ▏▎▍▌▋▊▉█')
163 ] if defined $opt{indicators} or $opt{spark};
164 $opt{input} = (@ARGV && $ARGV[0] =~ m/\A[-0-9]/) ? \@ARGV : undef
165         and undef $opt{interval};
166
167 $opt{'calc-format'} = sub { sprintf '%*.*f', 0, 2, $_[0] };
168 $opt{'value-format'} = $opt{sexagesimal} ? sub {
169         my $s = abs($_[0]) + .5;
170         sprintf('%s%d:%02d:%02d', $_[0] < 0 && '-', $s/3600, $s/60%60, $s%60);
171 } : $opt{units} && sub {
172         my $unit = (
173                 log(abs $_[0] || 1) / log(10)
174                 - 3 * (abs($_[0]) < .9995)   # shift to smaller unit if below 1
175                 + 1e-15  # float imprecision
176         );
177         my $decimal = ($unit % 3) == ($unit < 0);
178         $unit -= log($decimal ? .995 : .9995) / log(10);  # rounded
179         $decimal = ($unit % 3) == ($unit < 0);
180         $decimal &&= $_[0] !~ /^-?0*[0-9]{1,3}$/;  # integer 0..999
181         sprintf('%*.*f%1s',
182                 3 + ($_[0] < 0), # digits plus optional negative sign
183                 $decimal,  # tenths
184                 $_[0] / 1000 ** int($unit/3),  # number
185                 $#{$opt{units}} * 1.5 < abs $unit ? sprintf('e%d', $unit) :
186                         $opt{units}->[$unit/3]  # suffix
187         );
188 } and $opt{reformat}++;
189 $opt{'value-format'} ||= sub { sprintf '%.8g', $_[0] };
190
191
192 my (@lines, @values, @order, %uniq);
193
194 $SIG{$_} = \&show_stat for $opt{'signal-stat'} || ();
195 $SIG{ALRM} = sub {
196         show_lines();
197         alarm $opt{interval} if defined $opt{interval} and $opt{interval} > 0;
198 };
199 $SIG{INT} = \&show_exit;
200
201 if (defined $opt{interval}) {
202         $opt{interval} ||= 1;
203         alarm $opt{interval} if $opt{interval} > 0;
204
205         eval {
206                 require Tie::Array::Sorted;
207                 tie @order, 'Tie::Array::Sorted', sub { $_[1] <=> $_[0] };
208         } or warn $@, "Expect slowdown with large datasets!\n"
209                 unless $opt{count};
210 }
211
212 my $float = qr<[0-9]* [.]? [0-9]+ (?: e[+-]?[0-9]+ )?>; # positive numberish
213 my $valmatch = $opt{anchor} // qr/\A/;
214 $valmatch .= !$opt{count} ? qr/( \h* -? $float |)/ :
215         $opt{anchor} ? qr/(\S*)/ : qr/(.*)/;
216
217 while (defined ($_ = $opt{input} ? shift @{ $opt{input} } : readline)) {
218         s/\r?\n\z//;
219         my $valnum;
220         if ($opt{count}) {
221                 $valnum = m/$valmatch/ && $1;
222                 $uniq{$valnum}++ and next;
223                 push @values, $valnum;
224                 s/\A/\n /;
225         }
226         else {
227                 s/\A\h*// unless $opt{unmodified};
228                 $valnum = s/$valmatch/\n/ && $1;
229                 push @values, $valnum;
230                 push @order, $valnum if length $valnum;
231         }
232
233         if (defined $opt{trim} and defined $valnum) {
234                 my $trimpos = abs $opt{trim};
235                 $trimpos -= length $valnum if $opt{unmodified};
236                 if ($trimpos <= 1) {
237                         $_ = substr $_, 0, 2;
238                 }
239                 elsif (length > $trimpos) {
240                         # cut and replace (intentional lvalue for speed, contrary to PBP)
241                         substr($_, $trimpos - 1) = $opt{ascii} ? '>' : '…';
242                 }
243         }
244         push @lines, $_;
245 }
246 continue {
247         show_lines() if defined $opt{interval} and $opt{interval} < 0
248                 and $. % $opt{interval} == 0;
249 }
250
251 $SIG{INT} = 'DEFAULT';
252
253 sub color {
254         $opt{color} and defined $_[0] or return '';
255         return "\e[$_[0]m" if defined wantarray;
256         $_ = color(@_) . $_ . color(0) if defined;
257 }
258
259 sub show_lines {
260
261 state $nr = $opt{hidemin} ? $opt{hidemin}->($#lines) : 0;
262 @lines > $nr or return;
263
264 my $limit = $opt{hidemax} ? $opt{hidemax}->($#lines, $nr) : $#lines;
265
266 if ($opt{count}) {
267         $_ = $uniq{$_} for @values[$nr .. $limit];
268         @order = @values;
269 }
270
271 @order = sort { $b <=> $a } @order unless tied @order;
272 my $maxval = $opt{maxval} // (
273         $opt{hidemax} ? max grep { length } @values[$nr .. $limit] :
274         $order[0]
275 ) // 0;
276 my $minval = $opt{minval} // min $order[-1] // (), 0;
277 my $range = $maxval - $minval;
278 $range &&= log $range if $opt{log};
279 my $lenval = $opt{'value-length'} // max map { length } @order;
280 my $len    = defined $opt{trim} && $opt{trim} <= 0 ? -$opt{trim} + 1 :
281         max(map { length $values[$_] && length $lines[$_] } $nr .. $limit)
282         // 0;  # left padding
283 my $size   = defined $opt{width} && $range &&
284         ($opt{width} - $lenval - $len - !!$opt{indicators});  # bar multiplication
285
286 my @barmark;
287 if ($opt{markers} and $size > 0) {
288         for my $markspec (split /\h/, $opt{markers}) {
289                 my ($char, $func) = split //, $markspec, 2;
290                 my $increment = $func =~ s/[+]\z//;
291                 my @pos = eval {
292                         if ($func eq 'avg') {
293                                 return sum(@order) / @order;
294                         }
295                         elsif ($func =~ /\A([0-9.]+)v\z/) {
296                                 $1 <= 100 or die(
297                                         "Invalid marker $char: percentile $1 out of bounds\n"
298                                 );
299                                 my $index = $#order * $1 / 100;
300                                 return ($order[$index] + $order[$index + .5]) / 2;
301                         }
302                         elsif ($func =~ /\A-?[0-9.]+\z/) {
303                                 return $func;
304                         }
305                         elsif ($func =~ /\A\/($float)\z/) {
306                                 my @range = my $multiple = my $next = $1;
307                                 while ($next < $maxval) {
308                                         $multiple *= 10 if $opt{log};
309                                         push @range, $next += $multiple;
310                                 }
311                                 return @range;
312                         }
313                         else {
314                                 die "Unknown marker $char: $func\n";
315                         }
316                 };
317                 @pos or do {
318                         warn $@ if $@;
319                         next;
320                 };
321                 for my $pos (@pos) {
322                         $pos -= $minval;
323                         $pos &&= log $pos if $opt{log};
324                         $pos >= 0 or next;
325                         $increment ||= $minval && !$pos;
326                         color(36) for $barmark[$pos / $range * $size + $increment] = $char;
327                 }
328         }
329
330         state $lastmax = $maxval;
331         if ($maxval > $lastmax) {
332                 print ' ' x ($lenval + $len);
333                 print color(90);
334                 printf '%-*s',
335                         ($lastmax - $minval) * $size / $range + .5,
336                         '-' x (($values[$nr - 1] - $minval) * $size / $range);
337                 print color(92);
338                 say '+' x (($range - $lastmax) * $size / $range + .5);
339                 print color(0);
340                 $lastmax = $maxval;
341         }
342 }
343
344 say(
345         color(31), sprintf('%*s', $lenval, $minval),
346         color(90), '-', color(36), '+',
347         color(32), sprintf('%*s', $size - 3, $maxval),
348         color(90), '-', color(36), '+',
349         color(0),
350 ) if $opt{header};
351
352 while ($nr <= $limit) {
353         my $val = $values[$nr];
354         my $rel;
355         if (length $val) {
356                 $rel = $val - $minval;
357                 $rel &&= log $rel if $opt{log};
358                 $rel = min(1, $rel / $range) if $range; # 0..1
359         }
360         my $color = !length $val || !$opt{palette} ? undef :
361                 $val == $order[0] ? $opt{palette}->[-1] : # max
362                 $val == $order[-1] ? $opt{palette}->[0] : # min
363                 $opt{palette}->[ $rel * ($#{$opt{palette}} - 1) + 1 ];
364         my $indicator = $opt{indicators} && $opt{indicators}->[
365                 !length($val) || !$#{$opt{indicators}} ? 0 : # blank
366                 $#{$opt{indicators}} < 2 ? 1 :
367                 $val >= $order[0] ? -1 :
368                 $rel * ($#{$opt{indicators}} - 1e-14) + 1
369         ];
370
371         if ($opt{spark}) {
372                 say '' if $opt{width} and $nr and $nr % $opt{width} == 0;
373                 print color($color), $_ for $indicator;
374                 next;
375         }
376         print $indicator if defined $indicator;
377
378         if (length $val) {
379                 $val = sprintf("%*s", $lenval,
380                         $opt{reformat} ? $opt{'value-format'}->($val) : $val
381                 );
382                 color($color) for $val;
383         }
384         my $line = $lines[$nr] =~ s/\n/$val/r;
385         if (not length $val) {
386                 say $line;
387                 next;
388         }
389         printf '%-*s', $len + length($val), $line;
390         if ($rel and $size) {
391                 print $barmark[$_] // $opt{'graph-format'}
392                         for 1 .. $rel * $size + .5;
393         }
394         say '';
395 }
396 continue {
397         $nr++;
398 }
399 say $opt{palette} ? color(0) : '' if $opt{spark};
400 %uniq = () if $opt{interval} and $opt{count};
401
402         return $nr;
403 }
404
405 sub show_stat {
406         my %vars = (
407                 count => int @order,
408                 lines => int @lines,
409         );
410         my $linemin = !$opt{hidemin} ? 0 :
411                 ($vars{start} = $opt{hidemin}->($#lines));
412         my $linemax = !$opt{hidemax} ? $#lines :
413                 ($vars{end} = $opt{hidemax}->($#lines, $vars{start}));
414         if (@order) {
415                 $vars{partsum} = sum(0, grep {length} @values[$linemin .. $linemax])
416                         if $linemin <= $linemax and ($opt{hidemin} or $opt{hidemax});
417                 %vars = (%vars,
418                         sum => sum(@order),
419                         min => $order[-1],
420                         max => $order[0],
421                 );
422                 $vars{avg} = $vars{sum} / @order;
423         }
424         say varfmt($opt{report}, \%vars);
425         return 1;
426 }
427
428 sub varfmt {
429         my ($fmt, $vars) = @_;
430         $fmt =~ s[\$\{ \h*+ ((?: [^{}]++ | \{(?1)\} )+) \}]{
431                 my ($name, $op, $cmd) = split /\s*([;:])/, $1, 2;
432                 my $format = $name =~ s/\+// || $name !~ s/\#// && $opt{reformat};
433                 local $_ = $vars->{$name};
434                 defined && do {
435                         $_ = $opt{'value-format'}->($_) if $format;
436                         if ($cmd and $op eq ':') {
437                                 $_ = varfmt($cmd, $vars);
438                         }
439                         elsif ($cmd) {
440                                 eval $cmd;
441                                 warn "Error in \$$name report: $@" if $@;
442                         }
443                         $_;
444                 }
445         }eg;
446         return $fmt;
447 }
448
449 sub show_exit {
450         show_lines();
451         show_stat() if $opt{stat};
452         exit 130 if @_;  # 0x80+signo
453         exit;
454 }
455
456 show_exit();
457
458 __END__
459 Usage:                                               /\_/\
460   barcat [OPTIONS] [FILES|NUMBERS]                  (=•.•=)
461                                                     (u   u)
462 Options:
463   -a, --[no-]ascii         Restrict user interface to ASCII characters
464   -C, --[no-]color         Force colored output of values and bar markers
465   -c, --count              Omit repetitions and count the number of
466                            occurrences
467   -f, --field=([+]N|REGEXP)
468                            Compare values after a given number of whitespace
469                            separators
470       --header             Prepend a chart axis with minimum and maximum
471                            values labeled
472   -H, --human-readable     Format values using SI unit prefixes
473       --sexagesimal        Convert seconds to HH:MM:SS time format
474   -t, --interval[=(N|-LINES)]
475                            Output partial progress every given number of
476                            seconds or input lines
477   -l, --length=[-]SIZE[%]  Trim line contents (between number and bars)
478   -L, --limit=[N|[-]START(-[END]|+N)]
479                            Select a range of lines to display
480   -e, --log                Logarithmic (exponential) scale instead of linear
481       --graph-format=CHAR  Glyph to repeat for the graph line
482   -m, --markers=FORMAT     Statistical positions to indicate on bars
483       --min=N, --max=N     Bars extend from 0 or the minimum value if lower
484       --palette=(PRESET|COLORS)
485                            Override colors of parsed numbers
486   -_, --spark              Replace lines by sparklines
487       --indicators[=CHARS] Prefix a unicode character corresponding to each
488                            value
489   -s, --stat               Total statistics after all data
490   -u, --unmodified         Do not reformat values, keeping leading whitespace
491       --value-length=SIZE  Reserved space for numbers
492   -w, --width=COLUMNS      Override the maximum number of columns to use
493   -h, --usage              Overview of available options
494       --help               Full pod documentation
495   -V, --version            Version information
496
497 =encoding utf8
498
499 =head1 NAME
500
501 barcat - concatenate texts with graph to visualize values
502
503 =head1 SYNOPSIS
504
505 B<barcat> [I<options>] [I<file>... | I<numbers>]
506
507 =head1 DESCRIPTION
508
509 Visualizes relative sizes of values read from input
510 (parameters, file(s) or STDIN).
511 Contents are concatenated similar to I<cat>,
512 but numbers are reformatted and a bar graph is appended to each line.
513
514 It can has various options for input and output (re)formatting,
515 but remains limited to one-dimensional charts.
516 For more complex graphing needs
517 you'll need a larger animal like I<gnuplot>.
518
519 =head1 OPTIONS
520
521 =over
522
523 =item B<-a>, B<-->[B<no->]B<ascii>
524
525 Restrict user interface to ASCII characters,
526 replacing default UTF-8 by their closest approximation.
527 Input is always interpreted as UTF-8 and shown as is.
528
529 =item B<-C>, B<-->[B<no->]B<color>
530
531 Force colored output of values and bar markers.
532 Defaults on if output is a tty,
533 disabled otherwise such as when piped or redirected.
534 Can also be disabled by setting B<-M>
535 or the I<NO_COLOR> environment variable.
536
537 =item B<-c>, B<--count>
538
539 Omit repetitions and count the number of occurrences.
540 Similar to piping input through C<sort | uniq -c>
541 but keeping the order of first appearances.
542
543 Lines are omitted if they (or a specified field) are identical,
544 and the amount of matches is prepended and used as values
545 for bars and subsequent statistics.
546
547 =item B<-f>, B<--field>=([B<+>]I<number> | I<regexp>)
548
549 Compare values after a given number of whitespace separators,
550 or matching a regular expression.
551
552 Unspecified or B<-f0> means values are at the start of each line.
553 With B<-f1> the second word is taken instead.
554 A string can indicate the starting position of a value
555 (such as B<-f:> if preceded by colons),
556 or capture the numbers itself,
557 for example B<-f'(\d+)'> for the first digits anywhere.
558 A shorthand for this is C<+0>, or C<+N> to find the Nth number.
559
560 =item B<--header>
561
562 Prepend a chart axis with minimum and maximum values labeled.
563
564 =item B<-H>, B<--human-readable>
565
566 Format values using SI unit prefixes,
567 turning long numbers like C<12356789> into C<12.4M>.
568 Also changes an exponent C<1.602176634e-19> to C<160.2z>.
569 Short integers are aligned but kept without decimal point.
570
571 =item B<--sexagesimal>
572
573 Convert seconds to HH:MM:SS time format.
574
575 =item B<-t>, B<--interval>[=(I<seconds> | B<->I<lines>)]
576
577 Output partial progress every given number of seconds or input lines.
578 An update can also be forced by sending a I<SIGALRM> alarm signal.
579
580 =item B<-l>, B<--length>=[B<->]I<size>[B<%>]
581
582 Trim line contents (between number and bars)
583 to a maximum number of characters.
584 The exceeding part is replaced by an abbreviation sign,
585 unless B<--length=0>.
586
587 Prepend a dash (i.e. make negative) to enforce padding
588 regardless of encountered contents.
589
590 =item B<-L>, B<--limit>=[I<count> | [B<->]I<start>(B<->[I<end>] | B<+>I<count>)]
591
592 Select a range of lines to display.
593 A single integer indicates the last line number (like I<head>),
594 or first line counting from the bottom if negative (like I<tail>).
595
596 A range consists of a starting line number followed by either
597 a dash C<-> to an optional end, or plus sign C<+> with count.
598
599 All hidden input is still counted and analyzed for statistics,
600 but disregarded for padding and bar size.
601
602 =item B<-e>, B<--log>
603
604 Logarithmic (B<e>xponential) scale instead of linear
605 to compare orders of magnitude.
606
607 =item B<--graph-format>=I<character>
608
609 Glyph to repeat for the graph line.
610 Defaults to a dash C<->.
611
612 =item B<-m>, B<--markers>=I<format>
613
614 Statistical positions to indicate on bars.
615 A single indicator glyph precedes each position:
616
617 =over 2
618
619 =item I<number>
620
621 Exact value to match on the axis.
622 A vertical bar at the zero crossing is displayed by C<|0>
623 for negative values.
624 For example C<π3.14> would locate pi.
625
626 =item B</>I<interval>
627
628 Repeated at every multiple of a number.
629 For example C<:/1> for a grid at every integer.
630
631 =item I<percentage>B<v>
632
633 Ranked value at the given percentile.
634 The default shows C<+> at C<50v> for the mean or median;
635 the middle value or average between middle values.
636 One standard deviation right of the mean is at about C<68.3v>.
637 The default includes C<< >31.73v <68.27v >>
638 to encompass all I<normal> results, or 68% of all entries, by I<< <--> >>.
639
640 =item B<avg>
641
642 Matches the average;
643 the sum of all values divided by the number of counted lines.
644 Indicated by default as C<=>.
645
646 =back
647
648 =item B<--min>=I<number>, B<--max>=I<number>
649
650 Bars extend from 0 or the minimum value if lower,
651 to the largest value encountered.
652 These options can be set to customize this range.
653
654 =item B<--palette>=(I<preset> | I<color>...)
655
656 Override colors of parsed numbers.
657 Can be any CSI escape, such as C<90> for default dark gray,
658 or alternatively C<1;30> for bright black.
659
660 In case of additional colors,
661 the last is used for values equal to the maximum, the first for minima.
662 If unspecified, these are green and red respectively (C<31 90 32>).
663 Multiple intermediate colors will be distributed
664 relative to the size of values.
665
666 A non-numeric name can refer to a predefined color scheme:
667
668 =over 8
669
670 =item B<whites>
671
672 Minimal set of monochrome brightnesses.
673
674 =item B<grays>
675
676 Utilize the 24 grayscale ramp in 256-color terminals.
677
678 =item B<fire>
679
680 Gradient red to white in 7 out of 16 colors.
681
682 =item B<fire256>
683
684 Extended to 17 colors out of 256.
685
686 =item B<rainbow>
687
688 Saturated red to green to blue to red.
689
690 =item B<random>
691
692 All 215 extended colors in unrelated orders.
693
694 =back
695
696 =item B<-_>, B<--spark>
697
698 Replace lines by I<sparklines>,
699 single characters (configured by B<--indicators>)
700 corresponding to input values.
701
702 =item B<--indicators>[=I<characters>]
703
704 Prefix a unicode character corresponding to each value.
705 The first specified character will be used for non-values,
706 the remaining sequence will be distributed over the range of values.
707 Unspecified, block fill glyphs U+2581-2588 will be used.
708
709 =item B<-s>, B<--stat>
710
711 Total statistics after all data.
712
713 While processing (possibly a neverending pipe),
714 intermediate results are also shown on signal I<SIGINFO> if available (control+t on BSDs)
715 or I<SIGQUIT> otherwise (ctrl+\ on linux).
716
717 =item B<-u>, B<--unmodified>
718
719 Do not reformat values, keeping leading whitespace.
720 Keep original value alignment, which may be significant in some programs.
721
722 =item B<--value-length>=I<size>
723
724 Reserved space for numbers.
725
726 =item B<-w>, B<--width>=I<columns>
727
728 Override the maximum number of columns to use.
729 Appended graphics will extend to fill up the entire screen,
730 otherwise determined by the environment variable I<COLUMNS>
731 or by running the I<tput> command.
732
733 =item B<-h>, B<--usage>
734
735 Overview of available options.
736
737 =item B<--help>
738
739 Full pod documentation
740 as rendered by perldoc.
741
742 =item B<-V>, B<--version>
743
744 Version information.
745
746 =back
747
748 =head1 EXAMPLES
749
750 Draw a sine wave:
751
752     seq 30 | awk '{print sin($1/10)}' | barcat
753
754 Compare file sizes (with human-readable numbers):
755
756     du -d0 -b * | barcat -H
757
758 Same from formatted results, selecting the first numeric value:
759
760     tree -s --noreport | barcat -H -f+
761
762 Compare media metadata, like image size or play time:
763
764     exiftool -T -p '$megapixels ($imagesize) $filename' * | barcat
765
766     exiftool -T -p '$duration# $avgbitrate# $filename' * | barcat --sexagesimal
767
768     find -type f -print0 | xargs -0 -L1 \
769     ffprobe -show_format -of json -v error |
770     jq -r '.format|.duration+" "+.bit_rate+" "+.filename' | barcat --sex
771
772 Memory usage of user processes with long names truncated:
773
774     ps xo rss,pid,cmd | barcat -l40
775
776 Monitor network latency from prefixed results:
777
778     ping google.com | barcat -f'time=\K' -t
779
780 Commonly used after counting, eg letter frequencies in text files:
781
782     cat /usr/share/games/fortunes/*.u8 |
783     perl -CS -nE 'say for grep length, split /\PL*/, uc' |
784     sort | uniq -c | barcat
785
786 Users on the current server while preserving order:
787
788     users | tr ' ' '\n' | barcat -c
789
790 Number of HTTP requests per day:
791
792     barcat -cf'\[([^:]+)' httpd/access.log
793
794 Any kind of database query results, preserving returned alignment:
795
796     echo 'SELECT sin(value * .1) FROM generate_series(0, 30) value' |
797     psql -t | barcat -u
798
799 In PostgreSQL from within the client; a fancy C<\dt+> perhaps:
800
801     > SELECT schemaname, relname, pg_total_relation_size(relid)
802       FROM pg_statio_user_tables ORDER BY idx_blks_hit
803       \g |barcat -uHf+
804
805 Same thing in SQLite (requires the sqlite3 client):
806
807     > .once |barcat -Hf+
808     > SELECT name, sum(pgsize) FROM dbstat GROUP BY 1;
809
810 Earthquakes worldwide magnitude 1+ in the last 24 hours:
811
812     curl https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/1.0_day.csv |
813     column -ts, -n | barcat -f4 -u -l80%
814
815 External datasets, like movies per year:
816
817     curl https://github.com/prust/wikipedia-movie-data/raw/master/movies.json -L |
818     jq .[].year | uniq -c | barcat
819
820 Pokémon height comparison:
821
822     curl https://github.com/Biuni/PokemonGO-Pokedex/raw/master/pokedex.json -L |
823     jq -r '.pokemon[] | [.height,.num,.name] | join(" ")' | barcat
824
825 USD/EUR exchange rate from CSV provided by the ECB:
826
827     curl https://sdw.ecb.europa.eu/export.do \
828          -Gd 'node=SEARCHRESULTS&q=EXR.D.USD.EUR.SP00.A&exportType=csv' |
829     barcat -f',\K' --value-length=7
830
831 Total population history in XML from the World Bank:
832
833     curl http://api.worldbank.org/v2/country/1W/indicator/SP.POP.TOTL |
834     xmlstarlet sel -t -m '*/*' -v wb:date -o ' ' -v wb:value -n |
835     barcat -f1 -H --markers=+/1e9
836
837 Population and other information for all countries:
838
839     curl http://download.geonames.org/export/dump/countryInfo.txt |
840     grep -v '^#\s' | column -ts$'\t' -n | barcat -f+2 -e -u -l150 -s
841
842 And of course various Git statistics, such commit count by year:
843
844     git log --pretty=%ci | cut -b-4 | uniq -c | barcat
845
846 Or the top 3 most frequent authors with statistics over all:
847
848     git shortlog -sn | barcat -L3 -s
849
850 Activity graph of the last days (substitute date C<-v-{}d> on BSD):
851
852     ( git log --pretty=%ci --since=30day | cut -b-10
853       seq 0 30 | xargs -i date +%F -d-{}day ) |
854     sort | uniq -c | awk '$1--' | barcat --spark
855
856 Sparkline graphics of simple input given as inline parameters:
857
858     barcat -_ 3 1 4 1 5 0 9 2 4
859
860 Misusing the spark functionality to draw a lolcat line:
861
862     seq $(tput cols) | barcat --spark --indicator=- --palette=rainbow
863
864 =head1 AUTHOR
865
866 Mischa POSLAWSKY <perl@shiar.org>
867
868 =head1 LICENSE
869
870 GPL3+.