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"
34 'usage|h' => sub { podexit() },
35 'help' => sub { podexit(-verbose => 2) },
36 ) or exit 64; # EX_USAGE
38 $opt{width} ||= $ENV{COLUMNS} || 80;
39 $opt{color} //= -t *STDOUT; # enable on tty
40 $opt{trim} *= $opt{width} / 100 if $opt{trimpct};
42 if (defined $opt{interval}) {
51 $SIG{INT} = 'IGNORE'; # continue after assumed eof
54 my $anchor = !defined $opt{field} ? qr/\A/ :
55 $opt{field} =~ /^[0-9]+$/ ? qr/(?:\S*\h+){$opt{field}}\K/ :
59 s/^\h*// unless $opt{unmodified};
60 push @values, s/$anchor ( \h* -? [0-9]* \.? [0-9]+ |)/\n/x && $1;
61 if (defined $opt{trim}) {
62 my $trimpos = abs $opt{trim};
66 elsif (length > $trimpos) {
67 substr($_, $trimpos - 1) = '…';
73 $SIG{INT} = 'DEFAULT';
78 @lines and @lines > $nr or return;
80 my @order = sort { $b <=> $a } grep { length } @values;
81 my $maxval = $order[0];
82 my $minval = min $order[-1], 0;
83 my $lenval = max map { length } @order;
84 my $len = defined $opt{trim} && $opt{trim} <= 0 ? -$opt{trim} + 1 :
85 max map { length $values[$_] && length $lines[$_] } 0 .. $#lines; # left padding
86 my $size = ($maxval - $minval) &&
87 ($opt{width} - $lenval - $len) / ($maxval - $minval); # bar multiplication
90 if ($opt{markers} // 1 and $size > 0) {
91 my sub orderpos { (($order[$_[0]] + $order[$_[0] + .5]) / 2 - $minval) * $size }
92 $barmark[ (sum(@order) / @order - $minval) * $size ] = '='; # average
93 $barmark[ orderpos($#order * .31731) ] = '>';
94 $barmark[ orderpos($#order * .68269) ] = '<';
95 $barmark[ orderpos($#order / 2) ] = '+'; # mean
96 $barmark[ -$minval * $size ] = '|' if $minval < 0; # zero
97 defined and $opt{color} and $_ = "\e[36m$_\e[0m" for @barmark;
99 state $lastmax = $maxval;
100 if ($maxval > $lastmax) {
101 print ' ' x ($lenval + $len);
102 printf "\e[90m" if $opt{color};
104 ($lastmax - $minval) * $size + .5,
105 '-' x (($values[$nr - 1] - $minval) * $size);
106 print "\e[92m" if $opt{color};
107 say '+' x (($maxval - $lastmax - $minval) * $size + .5);
108 print "\e[0m" if $opt{color};
113 while ($nr <= $#lines) {
114 my $val = $values[$nr];
116 my $color = !$opt{color} ? 0 :
117 $val == $order[0] ? 32 : # max
118 $val == $order[-1] ? 31 : # min
120 $val = sprintf "%*s", $lenval, $val;
121 $val = "\e[${color}m$val\e[0m" if $color;
123 my $line = $lines[$nr] =~ s/\n/$val/r;
124 printf '%-*s', $len + length($val), $line;
125 print $barmark[$_] // '-' for 1 .. $size && (($values[$nr] || 0) - $minval) * $size;
137 graph - append bar chart to input numbers
141 B<graph> [<options>] [<input>]
145 Visualizes relative sizes of values read from input (file(s) or STDIN).
146 Contents are concatenated similar to I<cat>,
147 but numbers are reformatted and a bar graph is appended to each line.
153 =item -c, --[no-]color
155 Force colored output of values and bar markers.
156 Defaults on if output is a tty,
157 disabled otherwise such as when piped or redirected.
159 =item -f, --field=(<number>|<regexp>)
161 Compare values after a given number of whitespace separators,
162 or matching a regular expression.
164 Unspecified or I<-f0> means values are at the start of each line.
165 With I<-f1> the second word is taken instead.
166 A string can indicate the starting position of a value
167 (such as I<-f:> if preceded by colons),
168 or capture the numbers itself,
169 for example I<-f'(\d+)'> for the first digits anywhere.
171 =item -t, --interval[=<seconds>]
173 Interval time to output partial progress.
175 =item -l, --length=[-]<size>[%]
177 Trim line contents (between number and bars)
178 to a maximum number of characters.
179 The exceeding part is replaced by an abbreviation sign,
180 unless C<--length=0>.
182 Prepend a dash (i.e. make negative) to enforce padding
183 regardless of encountered contents.
187 Statistical positions to indicate on bars.
188 Cannot be customized yet,
189 only disabled by providing an empty argument.
191 Any value enables all marker characters:
198 the sum of all values divided by the number of counted lines.
203 the middle value or average between middle values.
207 Standard deviation left of the mean.
208 Only 16% of all values are lower.
212 Standard deviation right of the mean.
213 The part between B<< <--> >> encompass all I<normal> results,
214 or 68% of all entries.
218 =item -u, --unmodified
220 Do not strip leading whitespace.
221 Keep original value alignment, which may be significant in some programs.
223 =item -w, --width=<columns>
225 Override the maximum number of columns to use.
226 Appended graphics will extend to fill up the entire screen.
232 Commonly used after counting, such as users on the current server:
234 users | sed 's/ /\n/g' | sort | uniq -c | graph
236 Letter frequencies in text files:
238 cat /usr/share/games/fortunes/*.u8 |
239 perl -CO -nE 'say for grep length, split /\PL*/, uc' |
240 sort | uniq -c | graph
242 Memory usage of user processes:
244 ps xo %mem,pid,cmd | graph -l40
246 Sizes (in megabytes) of all root files and directories:
250 Number of HTTP requests per day:
252 cat log/access.log | cut -d\ -f4 | cut -d: -f1 | uniq -c | graph
254 Any kind of database query with leading counts:
256 echo 'SELECT count(*),schemaname FROM pg_tables GROUP BY 2' |
259 Exchange rate USD/EUR history from CSV download provided by ECB:
261 curl https://sdw.ecb.europa.eu/export.do \
262 -Gd 'node=SEARCHRESULTS&q=EXR.D.USD.EUR.SP00.A&exportType=csv' |
263 grep '^[12]' | graph -f',\K'
265 Total population history from the World Bank dataset (XML):
267 curl http://api.worldbank.org/v2/country/1W/indicator/SP.POP.TOTL |
268 xmllint --xpath '//*[local-name()="date" or local-name()="value"]' - |
269 sed -r 's,</wb:value>,\n,g; s,(<[^>]+>)+, ,g' | graph -f1
271 Movies per year from prepared JSON data:
273 curl https://github.com/prust/wikipedia-movie-data/raw/master/movies.json |
274 jq '.[].year' | uniq -c | graph
276 Pokémon height comparison:
278 curl https://github.com/Biuni/PokemonGO-Pokedex/raw/master/pokedex.json |
279 jq -r '.pokemon[] | [.height,.num,.name] | join(" ")' | graph
281 Git statistics, such commit count by year:
283 git log --pretty=%ci | cut -b-4 | uniq -c | graph
285 Or the most frequent authors:
287 git shortlog -sn | graph | head -3
291 ping google.com | graph -f'time=\K' -t
295 Mischa POSLAWSKY <perl@shiar.org>