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 = $opt{field} ? qr/(?:\S*\h+){$opt{field}}\K/ : qr/^/;
57 s/^\h*// unless $opt{unmodified};
58 push @values, s/$anchor ( \h* -? [0-9]* \.? [0-9]+ |)/\n/x && $1;
59 if (defined $opt{trim}) {
60 my $trimpos = abs $opt{trim};
64 elsif (length > $trimpos) {
65 substr($_, $trimpos - 1) = '…';
71 $SIG{INT} = 'DEFAULT';
76 @lines and @lines > $nr or return;
78 my @order = sort { $b <=> $a } grep { length } @values;
79 my $maxval = $order[0];
80 my $minval = min $order[-1], 0;
81 my $lenval = max map { length } @order;
82 my $len = defined $opt{trim} && $opt{trim} <= 0 ? -$opt{trim} + 1 :
83 max map { length $values[$_] && length $lines[$_] } 0 .. $#lines; # left padding
84 my $size = ($maxval - $minval) &&
85 ($opt{width} - $lenval - $len) / ($maxval - $minval); # bar multiplication
88 if ($opt{markers} // 1 and $size > 0) {
89 my sub orderpos { (($order[$_[0]] + $order[$_[0] + .5]) / 2 - $minval) * $size }
90 $barmark[ (sum(@order) / @order - $minval) * $size ] = '='; # average
91 $barmark[ orderpos($#order * .31731) ] = '>';
92 $barmark[ orderpos($#order * .68269) ] = '<';
93 $barmark[ orderpos($#order / 2) ] = '+'; # mean
94 $barmark[ -$minval * $size ] = '|' if $minval < 0; # zero
95 defined and $opt{color} and $_ = "\e[36m$_\e[0m" for @barmark;
97 state $lastmax = $maxval;
98 if ($maxval > $lastmax) {
99 print ' ' x ($lenval + $len);
100 printf "\e[90m" if $opt{color};
102 ($lastmax - $minval) * $size + .5,
103 '-' x (($values[$nr - 1] - $minval) * $size);
104 print "\e[92m" if $opt{color};
105 say '+' x (($maxval - $lastmax - $minval) * $size + .5);
106 print "\e[0m" if $opt{color};
111 while ($nr <= $#lines) {
112 my $val = $values[$nr];
114 my $color = !$opt{color} ? 0 :
115 $val == $order[0] ? 32 : # max
116 $val == $order[-1] ? 31 : # min
118 $val = sprintf "%*s", $lenval, $val;
119 $val = "\e[${color}m$val\e[0m" if $color;
121 my $line = $lines[$nr] =~ s/\n/$val/r;
122 printf '%-*s', $len + length($val), $line;
123 print $barmark[$_] // '-' for 1 .. $size && (($values[$nr] || 0) - $minval) * $size;
135 graph - append bar chart to input numbers
139 B<graph> [<options>] [<input>]
143 Visualizes relative sizes of values read from input (file(s) or STDIN).
144 Contents are concatenated similar to I<cat>,
145 but numbers are reformatted and a bar graph is appended to each line.
151 =item -c, --[no-]color
153 Force colored output of values and bar markers.
154 Defaults on if output is a tty,
155 disabled otherwise such as when piped or redirected.
157 =item -f, --field=<number>
159 Compare values after a given number of whitespace separators.
160 Unspecified or I<-f0> means values are at the start of each line.
161 With I<-f1> the second word is taken instead.
163 =item -t, --interval[=<seconds>]
165 Interval time to output partial progress.
167 =item -l, --length=[-]<size>[%]
169 Trim line contents (between number and bars)
170 to a maximum number of characters.
171 The exceeding part is replaced by an abbreviation sign,
172 unless C<--length=0>.
174 Prepend a dash (i.e. make negative) to enforce padding
175 regardless of encountered contents.
179 Statistical positions to indicate on bars.
180 Cannot be customized yet,
181 only disabled by providing an empty argument.
183 Any value enables all marker characters:
190 the sum of all values divided by the number of counted lines.
195 the middle value or average between middle values.
199 Standard deviation left of the mean.
200 Only 16% of all values are lower.
204 Standard deviation right of the mean.
205 The part between B<< <--> >> encompass all I<normal> results,
206 or 68% of all entries.
210 =item -u, --unmodified
212 Do not strip leading whitespace.
213 Keep original value alignment, which may be significant in some programs.
215 =item -w, --width=<columns>
217 Override the maximum number of columns to use.
218 Appended graphics will extend to fill up the entire screen.
224 Commonly used after counting, such as users on the current server:
226 users | sed 's/ /\n/g' | sort | uniq -c | graph
228 Letter frequencies in text files:
230 cat /usr/share/games/fortunes/*.u8 |
231 perl -CO -nE 'say for grep length, split /\PL*/, uc' |
232 sort | uniq -c | graph
234 Memory usage of user processes:
236 ps xo %mem,pid,cmd | graph -l40
238 Sizes (in megabytes) of all root files and directories:
242 Number of HTTP requests per day:
244 cat log/access.log | cut -d\ -f4 | cut -d: -f1 | uniq -c | graph
246 Any kind of database query with leading counts:
248 echo 'SELECT count(*),schemaname FROM pg_tables GROUP BY 2' |
251 Exchange rate USD/EUR history from CSV download provided by ECB:
253 curl https://sdw.ecb.europa.eu/export.do \
254 -Gd 'node=SEARCHRESULTS&q=EXR.D.USD.EUR.SP00.A&exportType=csv' |
255 awk -F, '{RS="\r\n"} /^[12]/{print $1,$2}' | graph -f1
257 Total population history from the World Bank dataset (XML):
259 curl http://api.worldbank.org/v2/country/1W/indicator/SP.POP.TOTL |
260 xmllint --xpath '//*[local-name()="date" or local-name()="value"]' - |
261 sed -r 's,</wb:value>,\n,g; s,(<[^>]+>)+, ,g' | graph -f1
263 Movies per year from prepared JSON data:
265 curl https://github.com/prust/wikipedia-movie-data/raw/master/movies.json |
266 jq '.[].year' | uniq -c | graph
268 Pokémon height comparison:
270 curl https://github.com/Biuni/PokemonGO-Pokedex/raw/master/pokedex.json |
271 jq -r '.pokemon[] | [.height,.num,.name] | join(" ")' | graph
273 Git statistics, such commit count by year:
275 git log --pretty=%ci | cut -b-4 | uniq -c | graph
277 Or the most frequent authors:
279 git shortlog -sn | graph | head -3
284 perl -pe '$|=1; print s/ time=(.*)// ? "$1 for " : "> "' | graph -t
288 Mischa POSLAWSKY <perl@shiar.org>