5 use List::Util qw( min max sum );
6 use open qw( :std :utf8 );
7 use experimental qw( lexical_subs );
11 use Getopt::Long '2.33', qw( :config gnu_getopt );
14 Pod::Usage::pod2usage(-exitval => 0, -perldocopt => '-oman', @_);
19 'C' => sub { $opt{color} = 0 },
22 'trim|length|l=s' => sub {
23 my ($optname, $optval) = @_;
24 $optval =~ s/%$// and $opt{trimpct}++;
25 $optval =~ m/^-?[0-9]+$/ or die(
26 "Value \"$optval\" invalid for option $optname",
27 " (number or percentage expected)\n"
35 'usage|h' => sub { podexit() },
36 'help' => sub { podexit(-verbose => 2) },
37 ) or exit 64; # EX_USAGE
39 $opt{width} ||= $ENV{COLUMNS} || 80;
40 $opt{color} //= -t *STDOUT; # enable on tty
41 $opt{trim} *= $opt{width} / 100 if $opt{trimpct};
43 if (defined $opt{interval}) {
52 $SIG{INT} = 'IGNORE'; # continue after assumed eof
55 my $anchor = !defined $opt{field} ? qr/\A/ :
56 $opt{field} =~ /^[0-9]+$/ ? qr/(?:\S*\h+){$opt{field}}\K/ :
60 s/^\h*// unless $opt{unmodified};
61 push @values, s/$anchor ( \h* -? [0-9]* \.? [0-9]+ |)/\n/x && $1;
62 if (defined $opt{trim}) {
63 my $trimpos = abs $opt{trim};
67 elsif (length > $trimpos) {
68 substr($_, $trimpos - 1) = '…';
74 $SIG{INT} = 'DEFAULT';
79 @lines and @lines > $nr or return;
81 my @order = sort { $b <=> $a } grep { length } @values;
82 my $maxval = $order[0];
83 my $minval = min $order[-1], 0;
84 my $lenval = $opt{'value-length'} // max map { length } @order;
85 my $len = defined $opt{trim} && $opt{trim} <= 0 ? -$opt{trim} + 1 :
86 max map { length $values[$_] && length $lines[$_] } 0 .. $#lines; # left padding
87 my $size = ($maxval - $minval) &&
88 ($opt{width} - $lenval - $len) / ($maxval - $minval); # bar multiplication
91 if ($opt{markers} // 1 and $size > 0) {
92 my sub orderpos { (($order[$_[0]] + $order[$_[0] + .5]) / 2 - $minval) * $size }
93 $barmark[ (sum(@order) / @order - $minval) * $size ] = '='; # average
94 $barmark[ orderpos($#order * .31731) ] = '>';
95 $barmark[ orderpos($#order * .68269) ] = '<';
96 $barmark[ orderpos($#order / 2) ] = '+'; # mean
97 $barmark[ -$minval * $size ] = '|' if $minval < 0; # zero
98 defined and $opt{color} and $_ = "\e[36m$_\e[0m" for @barmark;
100 state $lastmax = $maxval;
101 if ($maxval > $lastmax) {
102 print ' ' x ($lenval + $len);
103 printf "\e[90m" if $opt{color};
105 ($lastmax - $minval) * $size + .5,
106 '-' x (($values[$nr - 1] - $minval) * $size);
107 print "\e[92m" if $opt{color};
108 say '+' x (($maxval - $lastmax - $minval) * $size + .5);
109 print "\e[0m" if $opt{color};
114 while ($nr <= $#lines) {
115 my $val = $values[$nr];
117 my $color = !$opt{color} ? 0 :
118 $val == $order[0] ? 32 : # max
119 $val == $order[-1] ? 31 : # min
121 $val = sprintf "%*s", $lenval, $val;
122 $val = "\e[${color}m$val\e[0m" if $color;
124 my $line = $lines[$nr] =~ s/\n/$val/r;
125 printf '%-*s', $len + length($val), $line;
126 print $barmark[$_] // '-' for 1 .. $size && (($values[$nr] || 0) - $minval) * $size;
138 graph - append bar chart to input numbers
142 B<graph> [<options>] [<input>]
146 Visualizes relative sizes of values read from input (file(s) or STDIN).
147 Contents are concatenated similar to I<cat>,
148 but numbers are reformatted and a bar graph is appended to each line.
154 =item -c, --[no-]color
156 Force colored output of values and bar markers.
157 Defaults on if output is a tty,
158 disabled otherwise such as when piped or redirected.
160 =item -f, --field=(<number>|<regexp>)
162 Compare values after a given number of whitespace separators,
163 or matching a regular expression.
165 Unspecified or I<-f0> means values are at the start of each line.
166 With I<-f1> the second word is taken instead.
167 A string can indicate the starting position of a value
168 (such as I<-f:> if preceded by colons),
169 or capture the numbers itself,
170 for example I<-f'(\d+)'> for the first digits anywhere.
172 =item -t, --interval[=<seconds>]
174 Interval time to output partial progress.
176 =item -l, --length=[-]<size>[%]
178 Trim line contents (between number and bars)
179 to a maximum number of characters.
180 The exceeding part is replaced by an abbreviation sign,
181 unless C<--length=0>.
183 Prepend a dash (i.e. make negative) to enforce padding
184 regardless of encountered contents.
188 Statistical positions to indicate on bars.
189 Cannot be customized yet,
190 only disabled by providing an empty argument.
192 Any value enables all marker characters:
199 the sum of all values divided by the number of counted lines.
204 the middle value or average between middle values.
208 Standard deviation left of the mean.
209 Only 16% of all values are lower.
213 Standard deviation right of the mean.
214 The part between B<< <--> >> encompass all I<normal> results,
215 or 68% of all entries.
219 =item -u, --unmodified
221 Do not strip leading whitespace.
222 Keep original value alignment, which may be significant in some programs.
224 =item --value-length=<size>
226 Reserved space for numbers.
228 =item -w, --width=<columns>
230 Override the maximum number of columns to use.
231 Appended graphics will extend to fill up the entire screen.
237 Commonly used after counting, such as users on the current server:
239 users | sed 's/ /\n/g' | sort | uniq -c | graph
241 Letter frequencies in text files:
243 cat /usr/share/games/fortunes/*.u8 |
244 perl -CO -nE 'say for grep length, split /\PL*/, uc' |
245 sort | uniq -c | graph
247 Memory usage of user processes:
249 ps xo %mem,pid,cmd | graph -l40
251 Sizes (in megabytes) of all root files and directories:
255 Number of HTTP requests per day:
257 cat log/access.log | cut -d\ -f4 | cut -d: -f1 | uniq -c | graph
259 Any kind of database query with leading counts:
261 echo 'SELECT count(*),schemaname FROM pg_tables GROUP BY 2' |
264 Exchange rate USD/EUR history from CSV download provided by ECB:
266 curl https://sdw.ecb.europa.eu/export.do \
267 -Gd 'node=SEARCHRESULTS&q=EXR.D.USD.EUR.SP00.A&exportType=csv' |
268 grep '^[12]' | graph -f',\K' --value-length=7
270 Total population history from the World Bank dataset (XML):
272 curl http://api.worldbank.org/v2/country/1W/indicator/SP.POP.TOTL |
273 xmllint --xpath '//*[local-name()="date" or local-name()="value"]' - |
274 sed -r 's,</wb:value>,\n,g; s,(<[^>]+>)+, ,g' | graph -f1
276 Movies per year from prepared JSON data:
278 curl https://github.com/prust/wikipedia-movie-data/raw/master/movies.json |
279 jq '.[].year' | uniq -c | graph
281 Pokémon height comparison:
283 curl https://github.com/Biuni/PokemonGO-Pokedex/raw/master/pokedex.json |
284 jq -r '.pokemon[] | [.height,.num,.name] | join(" ")' | graph
286 Git statistics, such commit count by year:
288 git log --pretty=%ci | cut -b-4 | uniq -c | graph
290 Or the most frequent authors:
292 git shortlog -sn | graph | head -3
296 ping google.com | graph -f'time=\K' -t
300 Mischa POSLAWSKY <perl@shiar.org>