reuse relative value calculation
[barcat.git] / barcat
diff --git a/barcat b/barcat
index 1a1112c0ccdfa530e55b1792a7d25bf21f5381c1..c3c0dfd86faf8d56c773ca67a47a2062ab48dcb3 100755 (executable)
--- a/barcat
+++ b/barcat
@@ -6,7 +6,7 @@ use List::Util qw( min max sum );
 use open qw( :std :utf8 );
 use re '/msx';
 
 use open qw( :std :utf8 );
 use re '/msx';
 
-our $VERSION = '1.08';
+our $VERSION = '1.09';
 
 my %opt;
 if (@ARGV) {
 
 my %opt;
 if (@ARGV) {
@@ -28,6 +28,7 @@ GetOptions(\%opt,
        },
        'human-readable|H!',
        'sexagesimal!',
        },
        'human-readable|H!',
        'sexagesimal!',
+       'reformat!',
        'interval|t:i',
        'trim|length|l=s' => sub {
                my ($optname, $optval) = @_;
        'interval|t:i',
        'trim|length|l=s' => sub {
                my ($optname, $optval) = @_;
@@ -53,6 +54,7 @@ GetOptions(\%opt,
                        " (range expected)\n"
                );
        },
                        " (range expected)\n"
                );
        },
+       'log|e!',
        'header!',
        'markers|m=s',
        'graph-format=s' => sub {
        'header!',
        'markers|m=s',
        'graph-format=s' => sub {
@@ -116,7 +118,7 @@ $opt{trim}   *= $opt{width} / 100 if $opt{trimpct};
 $opt{units}   = [split //, ' kMGTPEZYyzafpn'.($opt{ascii} ? 'u' : 'μ').'m']
        if $opt{'human-readable'};
 $opt{anchor} //= qr/\A/;
 $opt{units}   = [split //, ' kMGTPEZYyzafpn'.($opt{ascii} ? 'u' : 'μ').'m']
        if $opt{'human-readable'};
 $opt{anchor} //= qr/\A/;
-$opt{'value-length'} = 6 if $opt{units};
+$opt{'value-length'} = 4 if $opt{units};
 $opt{'value-length'} = 1 if $opt{unmodified};
 $opt{'signal-stat'} //= exists $SIG{INFO} ? 'INFO' : 'QUIT';
 $opt{markers} //= '=avg >31.73v <68.27v +50v |0';
 $opt{'value-length'} = 1 if $opt{unmodified};
 $opt{'signal-stat'} //= exists $SIG{INFO} ? 'INFO' : 'QUIT';
 $opt{markers} //= '=avg >31.73v <68.27v +50v |0';
@@ -128,11 +130,10 @@ $opt{hidemin} = ($opt{hidemin} || 1) - 1;
 $opt{input} = (@ARGV && $ARGV[0] =~ m/\A[-0-9]/) ? \@ARGV : undef
        and undef $opt{interval};
 
 $opt{input} = (@ARGV && $ARGV[0] =~ m/\A[-0-9]/) ? \@ARGV : undef
        and undef $opt{interval};
 
-$opt{'sum-format'} = sub { sprintf '%.8g', $_[0] };
 $opt{'calc-format'} = sub { sprintf '%*.*f', 0, 2, $_[0] };
 $opt{'value-format'} = $opt{sexagesimal} ? sub {
 $opt{'calc-format'} = sub { sprintf '%*.*f', 0, 2, $_[0] };
 $opt{'value-format'} = $opt{sexagesimal} ? sub {
-       my $s = $_[0] + .5;
-       sprintf('%d:%02d:%02d', $s/3600, $s/60%60, $s%60);
+       my $s = abs($_[0]) + .5;
+       sprintf('%s%d:%02d:%02d', $_[0] < 0 && '-', $s/3600, $s/60%60, $s%60);
 } : $opt{units} && sub {
        my $unit = (
                log(abs $_[0] || 1) / log(10)
 } : $opt{units} && sub {
        my $unit = (
                log(abs $_[0] || 1) / log(10)
@@ -150,7 +151,8 @@ $opt{'value-format'} = $opt{sexagesimal} ? sub {
                $#{$opt{units}} * 1.5 < abs $unit ? sprintf('e%d', $unit) :
                        $opt{units}->[$unit/3]  # suffix
        );
                $#{$opt{units}} * 1.5 < abs $unit ? sprintf('e%d', $unit) :
                        $opt{units}->[$unit/3]  # suffix
        );
-};
+} and $opt{reformat}++;
+$opt{'value-format'} ||= sub { sprintf '%.8g', $_[0] };
 
 
 my (@lines, @values, @order);
 
 
 my (@lines, @values, @order);
@@ -197,10 +199,6 @@ while (defined ($_ = $opt{input} ? shift @{ $opt{input} } : readline)) {
                and $. % $opt{interval} == 0;
 }
 
                and $. % $opt{interval} == 0;
 }
 
-if ($opt{'zero-missing'}) {
-       push @values, (0) x 10;
-}
-
 $SIG{INT} = 'DEFAULT';
 
 sub color {
 $SIG{INT} = 'DEFAULT';
 
 sub color {
@@ -233,12 +231,13 @@ my $maxval = $opt{maxval} // (
 ) // 0;
 my $minval = $opt{minval} // min $order[-1] // (), 0;
 my $range = $maxval - $minval;
 ) // 0;
 my $minval = $opt{minval} // min $order[-1] // (), 0;
 my $range = $maxval - $minval;
+$range &&= log $range if $opt{log};
 my $lenval = $opt{'value-length'} // max map { length } @order;
 my $len    = defined $opt{trim} && $opt{trim} <= 0 ? -$opt{trim} + 1 :
        max map { length $values[$_] && length $lines[$_] }
                0 .. min $#lines, $opt{hidemax} || ();  # left padding
 my $size   = defined $opt{width} && $range &&
 my $lenval = $opt{'value-length'} // max map { length } @order;
 my $len    = defined $opt{trim} && $opt{trim} <= 0 ? -$opt{trim} + 1 :
        max map { length $values[$_] && length $lines[$_] }
                0 .. min $#lines, $opt{hidemax} || ();  # left padding
 my $size   = defined $opt{width} && $range &&
-       ($opt{width} - $lenval - $len - !!$opt{indicators}) / $range;  # bar multiplication
+       ($opt{width} - $lenval - $len - !!$opt{indicators});  # bar multiplication
 
 my @barmark;
 if ($opt{markers} and $size > 0) {
 
 my @barmark;
 if ($opt{markers} and $size > 0) {
@@ -249,7 +248,9 @@ if ($opt{markers} and $size > 0) {
                                return sum(@order) / @order;
                        }
                        elsif ($func =~ /\A([0-9.]+)v\z/) {
                                return sum(@order) / @order;
                        }
                        elsif ($func =~ /\A([0-9.]+)v\z/) {
-                               die "Invalid marker $char: percentile $1 out of bounds\n" if $1 > 100;
+                               $1 <= 100 or die(
+                                       "Invalid marker $char: percentile $1 out of bounds\n"
+                               );
                                my $index = $#order * $1 / 100;
                                return ($order[$index] + $order[$index + .5]) / 2;
                        }
                                my $index = $#order * $1 / 100;
                                return ($order[$index] + $order[$index + .5]) / 2;
                        }
@@ -265,8 +266,9 @@ if ($opt{markers} and $size > 0) {
                        next;
                };
                $pos -= $minval;
                        next;
                };
                $pos -= $minval;
+               $pos &&= log $pos if $opt{log};
                $pos >= 0 or next;
                $pos >= 0 or next;
-               color(36) for $barmark[$pos * $size] = $char;
+               color(36) for $barmark[$pos / $range * $size] = $char;
        }
 
        state $lastmax = $maxval;
        }
 
        state $lastmax = $maxval;
@@ -274,10 +276,10 @@ if ($opt{markers} and $size > 0) {
                print ' ' x ($lenval + $len);
                printf color(90);
                printf '%-*s',
                print ' ' x ($lenval + $len);
                printf color(90);
                printf '%-*s',
-                       ($lastmax - $minval) * $size + .5,
-                       '-' x (($values[$nr - 1] - $minval) * $size);
+                       ($lastmax - $minval) * $size / $range + .5,
+                       '-' x (($values[$nr - 1] - $minval) * $size / $range);
                print color(92);
                print color(92);
-               say '+' x (($range - $lastmax) * $size + .5);
+               say '+' x (($range - $lastmax) * $size / $range + .5);
                print color(0);
                $lastmax = $maxval;
        }
                print color(0);
                $lastmax = $maxval;
        }
@@ -286,14 +288,19 @@ if ($opt{markers} and $size > 0) {
 say(
        color(31), sprintf('%*s', $lenval, $minval),
        color(90), '-', color(36), '+',
 say(
        color(31), sprintf('%*s', $lenval, $minval),
        color(90), '-', color(36), '+',
-       color(32), sprintf('%*s', $size * $range - 3, $maxval),
+       color(32), sprintf('%*s', $size - 3, $maxval),
        color(90), '-', color(36), '+',
        color(0),
 ) if $opt{header};
 
 while ($nr <= $limit) {
        my $val = $values[$nr];
        color(90), '-', color(36), '+',
        color(0),
 ) if $opt{header};
 
 while ($nr <= $limit) {
        my $val = $values[$nr];
-       my $rel = length $val && $range && min(1, ($val - $minval) / $range);
+       my $rel;
+       if (length $val) {
+               $rel = $val - $minval;
+               $rel &&= log $rel if $opt{log};
+               $rel = min(1, $rel / $range) if $range; # 0..1
+       }
        my $color = !length $val || !$opt{palette} ? undef :
                $val == $order[0] ? $opt{palette}->[-1] : # max
                $val == $order[-1] ? $opt{palette}->[0] : # min
        my $color = !length $val || !$opt{palette} ? undef :
                $val == $order[0] ? $opt{palette}->[-1] : # max
                $val == $order[-1] ? $opt{palette}->[0] : # min
@@ -313,8 +320,9 @@ while ($nr <= $limit) {
        print $indicator if defined $indicator;
 
        if (length $val) {
        print $indicator if defined $indicator;
 
        if (length $val) {
-               $val = $opt{'value-format'} ? $opt{'value-format'}->($val) :
-                       sprintf "%*s", $lenval, $val;
+               $val = sprintf("%*s", $lenval,
+                       $opt{reformat} ? $opt{'value-format'}->($val) : $val
+               );
                color($color) for $val;
        }
        my $line = $lines[$nr] =~ s/\n/$val/r;
                color($color) for $val;
        }
        my $line = $lines[$nr] =~ s/\n/$val/r;
@@ -323,8 +331,10 @@ while ($nr <= $limit) {
                next;
        }
        printf '%-*s', $len + length($val), $line;
                next;
        }
        printf '%-*s', $len + length($val), $line;
-       print $barmark[$_] // $opt{'graph-format'}
-               for 1 .. $size && (($values[$nr] || 0) - $minval) * $size + .5;
+       if ($rel and $size) {
+               print $barmark[$_] // $opt{'graph-format'}
+                       for 1 .. $rel * $size + .5;
+       }
        say '';
 }
 continue {
        say '';
 }
 continue {
@@ -343,19 +353,19 @@ sub show_stat {
                        $linemin += @lines;
                        $linemax = @lines - $linemax;
                }
                        $linemin += @lines;
                        $linemax = @lines - $linemax;
                }
-               printf '%.8g of ', $opt{'sum-format'}->(
+               printf '%.8g of ', $opt{'value-format'}->(
                        sum(grep {length} @values[$linemin .. $linemax]) // 0
                );
        }
        if (@order) {
                my $total = sum @order;
                        sum(grep {length} @values[$linemin .. $linemax]) // 0
                );
        }
        if (@order) {
                my $total = sum @order;
-               printf '%s total', color(1) . $opt{'sum-format'}->($total) . color(0);
+               printf '%s total', color(1) . $opt{'value-format'}->($total) . color(0);
                printf ' in %d values', scalar @order;
                printf ' over %d lines', scalar @lines if @order != @lines;
                printf(' (%s min, %s avg, %s max)',
                printf ' in %d values', scalar @order;
                printf ' over %d lines', scalar @lines if @order != @lines;
                printf(' (%s min, %s avg, %s max)',
-                       color(31) . ($opt{'value-format'} || sub {$_[0]})->($order[-1]) . color(0),
-                       color(36) . ($opt{'value-format'} || $opt{'calc-format'})->($total / @order) . color(0),
-                       color(32) . ($opt{'value-format'} || sub {$_[0]})->($order[0]) . color(0),
+                       color(31) . ($opt{reformat} ? $opt{'value-format'} : sub {$_[0]})->($order[-1]) . color(0),
+                       color(36) . ($opt{reformat} ? $opt{'value-format'} : $opt{'calc-format'})->($total / @order) . color(0),
+                       color(32) . ($opt{reformat} ? $opt{'value-format'} : sub {$_[0]})->($order[0]) . color(0),
                );
        }
        say '';
                );
        }
        say '';
@@ -391,6 +401,7 @@ Options:
   -l, --length=[-]SIZE[%]  Trim line contents (between number and bars)
   -L, --limit[=(N|-LAST|START-[END])]
                            Stop output after a number of lines
   -l, --length=[-]SIZE[%]  Trim line contents (between number and bars)
   -L, --limit[=(N|-LAST|START-[END])]
                            Stop output after a number of lines
+  -e, --log                Logarithmic (exponential) scale instead of linear
       --graph-format=CHAR  Glyph to repeat for the graph line
   -m, --markers=FORMAT     Statistical positions to indicate on bars
       --min=N, --max=N     Bars extend from 0 or the minimum value if lower
       --graph-format=CHAR  Glyph to repeat for the graph line
   -m, --markers=FORMAT     Statistical positions to indicate on bars
       --min=N, --max=N     Bars extend from 0 or the minimum value if lower
@@ -501,6 +512,11 @@ A specific range can be given by two values.
 All input is still counted and analyzed for statistics,
 but disregarded for padding and bar size.
 
 All input is still counted and analyzed for statistics,
 but disregarded for padding and bar size.
 
+=item -e, --log
+
+Logarithmic (I<e>xponential) scale instead of linear
+to compare orders of magnitude.
+
 =item --graph-format=<character>
 
 Glyph to repeat for the graph line.
 =item --graph-format=<character>
 
 Glyph to repeat for the graph line.
@@ -587,7 +603,9 @@ Reserved space for numbers.
 =item -w, --width=<columns>
 
 Override the maximum number of columns to use.
 =item -w, --width=<columns>
 
 Override the maximum number of columns to use.
-Appended graphics will extend to fill up the entire screen.
+Appended graphics will extend to fill up the entire screen,
+otherwise determined by the environment variable I<COLUMNS>
+or by running the C<tput> command.
 
 =item -h, --usage
 
 
 =item -h, --usage
 
@@ -650,19 +668,26 @@ Number of HTTP requests per day:
 
     cat httpd/access.log | cut -d\  -f4 | cut -d: -f1 | uniq -c | barcat
 
 
     cat httpd/access.log | cut -d\  -f4 | cut -d: -f1 | uniq -c | barcat
 
-Any kind of database query with counts, preserving returned alignment:
+Any kind of database query results, preserving returned alignment:
 
 
-    echo 'SELECT count(*),schemaname FROM pg_tables GROUP BY 2' |
+    echo 'SELECT sin(value * .1) FROM generate_series(0, 30) value' |
     psql -t | barcat -u
 
     psql -t | barcat -u
 
-In PostgreSQL from within the client:
+In PostgreSQL from within the client; a fancy C<\dt+> perhaps:
+
+    > SELECT schemaname, relname, pg_total_relation_size(relid)
+      FROM pg_statio_user_tables ORDER BY idx_blks_hit
+      \g |barcat -uHf+
+
+Same thing in SQLite (requires the sqlite3 client):
 
 
-    > SELECT sin(generate_series(0, 3, .1)) \g |barcat
+    > .once |barcat -Hf+
+    > SELECT name, sum(pgsize) FROM dbstat GROUP BY 1;
 
 Earthquakes worldwide magnitude 1+ in the last 24 hours:
 
     curl https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/1.0_day.csv |
 
 Earthquakes worldwide magnitude 1+ in the last 24 hours:
 
     curl https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/1.0_day.csv |
-    column -tns, | barcat -f4 -u -l80%
+    column -ts, -n | barcat -f4 -u -l80%
 
 External datasets, like movies per year:
 
 
 External datasets, like movies per year:
 
@@ -689,7 +714,7 @@ Total population history in XML from the World Bank:
 Population and other information for all countries:
 
     curl http://download.geonames.org/export/dump/countryInfo.txt |
 Population and other information for all countries:
 
     curl http://download.geonames.org/export/dump/countryInfo.txt |
-    grep -v '^#\s' | column -tns$'\t' | barcat -f+2 -u -l150 -s
+    grep -v '^#\s' | column -ts$'\t' -n | barcat -f+2 -e -u -l150 -s
 
 And of course various Git statistics, such commit count by year:
 
 
 And of course various Git statistics, such commit count by year: