Raku Text::Plot
This repository has a Raku package for textual (terminal) plots.
Here is the list of functions:
- DONE DONE
text-list-plot
- DONE DONE
text-pareto-principle-plot
- DONE DONE
text-histogram
- TODO TODO
text-plot
- TODO TODO
text-bar-chart
Currently only text-list-plot
, text-pareto-principle-plot
, text-histogram
are implemented.
It would be nice to also have the function:
But that would require dependency on a certain statistical package.
(I think it is best to keep this package simple.)
Installation
From zef-ecosystem:
zef install Text::Plot
From GitHub:
zef install https://github.com/antononcube/Raku-Text-Plot.git
Usage examples
text-list-plot
Simple plot with y-values only:
use Text::Plot;
say text-list-plot((^30)>>.sqrt);
# +---+-------+--------+--------+--------+--------+--------+-+
# | |
# + * * * ** + 5.00
# | * * * ** |
# + * * ** + 4.00
# | * * ** |
# + ** * + 3.00
# | * * |
# + ** * + 2.00
# | * |
# + * * + 1.00
# | |
# + * + 0.00
# | |
# +---+-------+--------+--------+--------+--------+--------+-+
# 0.00 5.00 10.00 15.00 20.00 25.00 30.00
Plot using both x- and y-values, and with specified axes labels, y-tick-labels format, and plot width, height, and
title:
my @xs = (0, 0.2 ... 5);
say text-list-plot(@xs, @xs>>.sin,
x-label => 'x-points',
y-label => 'value',
y-tick-labels-format => '%10.2e',
width => 80,
height => 18,
title => 'SINE PLOT');
# SINE PLOT
# +---+-------------+-------------+-------------+-------------+-------------+----+
# | |
# + * * * * * + 1.00e+00
# | * * |
# | * * |
# + * * + 5.00e-01
# | * * | v
# | * | a
# | * | l
# + * * + 0.00e+00 u
# | * | e
# | |
# + * + -5.00e-01
# | * |
# | * * |
# + * * * * + -1.00e+00
# | |
# +---+-------------+-------------+-------------+-------------+-------------+----+
# 0.00 1.00 2.00 3.00 4.00 5.00
# x-points
Smallish plot with custom point character spec:
my @xs = (0, 0.05 ... 10);
say text-list-plot(@xs, -1 <<*>> @xs>>.sqrt,
point-char => '·',
width => 40,
height => 12);
# +--+-----+------+------+------+-----+--+
# + · + 0.00
# | · |
# + ··· + -0.50
# + ··· + -1.00
# + ···· + -1.50
# | ······ |
# + ······ + -2.00
# + ········ + -2.50
# + ········ + -3.00
# + · + -3.50
# +--+-----+------+------+------+-----+--+
# 0.00 2.00 4.00 6.00 8.00 10.00
Plot a list of two-element lists:
say text-list-plot((^@xs.elems Z @xs>>.cos).List, title => 'Some list of lists'),
# Some list of lists
# +---+------------+-----------+------------+------------+---+
# | |
# + *** ******* + 1.00
# | ** ** *** |
# + ** *** ** + 0.50
# | ** ** ** |
# | ** * ** |
# + ** * * + 0.00
# | ** * * |
# | ** * * |
# + ** * ** + -0.50
# | *** ** ** |
# + ******* ******* + -1.00
# | |
# +---+------------+-----------+------------+------------+---+
# 0.00 50.00 100.00 150.00 200.00
Here is a more complicated example using a randomly generated dataset, [AAp1, AAp2]:
use Data::Generators;
use Data::Summarizers;
my @dsRand = random-tabular-dataset(70, <x y>,
generators => [{ random-variate(NormalDistribution.new(4, 2), $_) },
{ random-variate(NormalDistribution.new(12, 3), $_) }]);
records-summary(@dsRand);
# +------------------------------+-------------------------------+
# | y | x |
# +------------------------------+-------------------------------+
# | Min => 6.728751608514747 | Min => -0.6342550664377802 |
# | 1st-Qu => 10.115185606162802 | 1st-Qu => 2.127501347786245 |
# | Mean => 11.977061937904317 | Mean => 3.701060338012048 |
# | Median => 11.884934801827065 | Median => 3.8810775440571463 |
# | 3rd-Qu => 14.052282754607848 | 3rd-Qu => 5.0641235729310745 |
# | Max => 17.825472186365246 | Max => 8.115595725328749 |
# +------------------------------+-------------------------------+
text-list-plot(@dsRand.map({ $_<x y> })>>.List,
x-limit => (-2, 10), y-limit => (0, 25),
title => 'Random Normal distribution variates')
# Random Normal distribution variates
# ++---------+--------+---------+--------+---------+--------++
# + + 25.00
# | |
# + + 20.00
# | * |
# | ** ** * * |
# + * * * * * *** * * + 15.00
# | * *** * * ** **** **** * * |
# + * ** * * * **** * * * * + 10.00
# | * * * * * ** |
# | * * |
# + + 5.00
# | |
# + + 0.00
# ++---------+--------+---------+--------+---------+--------++
# -2.00 0.00 2.00 4.00 6.00 8.00 10.00
Remark: The function text-list-plot
has camel case aliases for the multi-word named arguments.
For example, xLimit
for x-limit
and xTickLabelsFormat
for x-tick-labels-format
.
Here is an example of a multi-list plot:
say text-list-plot([([1,1], [2,5], [3,2], [4,5]),
([1,1], [3,3], [3,2]),
([1,3], [2,1], [5,2])], point-char => Whatever);
# +---+------------+-----------+------------+------------+---+
# | |
# + * * + 5.00
# | |
# + + 4.00
# | |
# | |
# + ▽ □ + 3.00
# | |
# | |
# + □ ▽ + 2.00
# | |
# + □ ▽ + 1.00
# | |
# +---+------------+-----------+------------+------------+---+
# 1.00 2.00 3.00 4.00 5.00
Remark: Note that the points [1,1]
and [3,2]
of the second list overlay the same points of first list.
text-pareto-principle-plot
Assume we have a data vector with all numeric or with all string elements.
The adherence of the data vector to the Pareto principle can be easily verified with the plots of
text-pareto-principle-plot
.
Here is an example with a numeric vector:
text-pareto-principle-plot( random-real(10, 300), title => 'Random reals')
# Random reals
# 0.00 0.17 0.33 0.50 0.67 0.83 1.00
# +---+-------+--------+--------+-------+--------+-------+---+
# | |
# + *************** + 1.00
# | ******** |
# + ***** + 0.80
# | **** |
# + **** + 0.60
# | ***** |
# + **** + 0.40
# | **** |
# + *** + 0.20
# | *** |
# | *** |
# + + 0.00
# +---+-------+--------+--------+-------+--------+-------+---+
# 0.00 50.00 100.00 150.00 200.00 250.00 300.00
Here is an example with a vector of strings:
text-pareto-principle-plot( random-pet-name(500), title => 'Random pet names')
# Random pet names
# 0.00 0.24 0.47 0.71 0.94
# +---+-----------+-----------+-----------+-----------+------+
# | |
# + ****** + 1.00
# | ****** |
# + ******* + 0.80
# | ****** |
# + ****** + 0.60
# | ******* |
# + ****** + 0.40
# | ******* |
# + **** + 0.20
# | **** |
# | ** |
# + + 0.00
# +---+-----------+-----------+-----------+-----------+------+
# 0.00 100.00 200.00 300.00 400.00
text-histogram
Here is a vector with normal distribution numbers:
my ($μ, $σ) = (5, 2);
my @data = (^500).map({ $μ + $σ * (2 * pi * (1 - rand)).cos * (- 2 * log rand).sqrt });
@data.elems
# 500
Here is a histogram with counts:
text-histogram(@data, 30, type => 'count', :filled, point-char => <* *>);
# +-------------+-----------------+-----------------+--------+
# | |
# + * + 50.00
# | * * |
# + * * * * * + 40.00
# | * ** * * ** |
# + * ** * * ** * + 30.00
# | * * ** * * ** * |
# + * ** * ** * * ** * * + 20.00
# | * ** * ** * * ** * * |
# + * * * ** * ** * * ** * ** * + 10.00
# | ** * * ** * ** * * ** * ** * * * |
# + * * * * ** * * ** * ** * * ** * ** * * * * + 0.00
# | |
# +-------------+-----------------+-----------------+--------+
# 0.00 5.00 10.00
Here is a histogram with density function estimate:
text-histogram(@data, 30, type => 'cdf', height => 20, :filled, point-char => <* ⏺>);
# +-------------+-----------------+-----------------+--------+
# | |
# + ⏺ ⏺ ⏺ ⏺ + 1.00
# | ⏺⏺ * * * * |
# | ⏺ ** * * * * |
# + ⏺ * ** * * * * + 0.80
# | ⏺* * ** * * * * |
# | ⏺ ** * ** * * * * |
# + * ** * ** * * * * + 0.60
# | ⏺ * ** * ** * * * * |
# | ⏺ * * ** * ** * * * * |
# + ⏺* * * ** * ** * * * * + 0.40
# | ** * * ** * ** * * * * |
# | ⏺ ** * * ** * ** * * * * |
# + ⏺ * ** * * ** * ** * * * * + 0.20
# | ⏺ ⏺* * ** * * ** * ** * * * * |
# | ⏺ ⏺ * ** * ** * * ** * ** * * * * |
# + ⏺ ⏺ ⏺ ⏺ ⏺* * * ** * ** * * ** * ** * * * * + 0.00
# | |
# +-------------+-----------------+-----------------+--------+
# 0.00 5.00 10.00
Remark: The second argument is for the number of histogram bins.
The value of the option :$type
is expected to be one of "count", "probability", "PDF", or "CDF".
Command Line Interface (CLI)
The package function text-list-plot
can be used through the corresponding CLI:
text-list-plot --help
# Usage:
# text-list-plot [<points> ...] [-p|--point-char=<Str>] [-w|--width[=Int]] [-h|--height[=Int]] [-t|--title=<Str>] [--xLabel|--x-label=<Str>] [--yLabel|--y-label=<Str>] [--xTickLabelsFormat|--x-tick-labels-format=<Str>] [--yTickLabelsFormat|--y-tick-labels-format=<Str>] -- Makes textual (terminal) plots.
# text-list-plot <words> [-p|--point-char=<Str>] [-w|--width[=Int]] [-h|--height[=Int]] [-t|--title=<Str>] [--xLabel|--x-label=<Str>] [--yLabel|--y-label=<Str>] [--xTickLabelsFormat|--x-tick-labels-format=<Str>] [--yTickLabelsFormat|--y-tick-labels-format=<Str>] -- Makes textual (terminal) plots by splitting a string of data points.
# text-list-plot [-p|--point-char=<Str>] [-w|--width[=Int]] [-h|--height[=Int]] [-t|--title=<Str>] [--xLabel|--x-label=<Str>] [--yLabel|--y-label=<Str>] [--xTickLabelsFormat|--x-tick-labels-format=<Str>] [--yTickLabelsFormat|--y-tick-labels-format=<Str>] -- Makes textual (terminal) plots from pipeline input
#
# [<points> ...] Data points.
# -p|--point-char=<Str> Plot points character. [default: '*']
# -w|--width[=Int] Width of the plot. (-1 for Whatever.) [default: -1]
# -h|--height[=Int] Height of the plot. (-1 for Whatever.) [default: -1]
# -t|--title=<Str> Title of the plot. [default: '']
# --xLabel|--x-label=<Str> Label of the X-axis. If Whatever, then no label is placed. [default: '']
# --yLabel|--y-label=<Str> Label of the Y-axis. If Whatever, then no label is placed. [default: '']
# --xTickLabelsFormat|--x-tick-labels-format=<Str> X-axis tick labels format. [default: '']
# --yTickLabelsFormat|--y-tick-labels-format=<Str> Y-axis tick labels format. [default: '']
# <words> String with data points.
Here is an example of a simple, y-axis values only call:
text-list-plot 33 12 21 10 3 4
Here is an example of 2D points call:
text-list-plot "22,32 10,39 13,32 14,20"
Here is an example pipeline:
raku -e 'say (^1000).roll(21)' | text-list-plot
Remark: Attempt is made plot's width and height are determined automatically, using terminal's number of columns and
lines. If that fails width=60
is used. In the pipeline example above text-list-plot
fails to automatically determine
the width and height. (The other example do succeed.)
Implementation notes
The package functions and their signatures design are easy to come up with, but it is very helpful to have a "good
example" to follow.
I consider the R-package "txtplot", [BB1], to be such good example.
There are at least three Python packages for text plots, but only tried them out once. None was as complete and "nice"
as the R-package "txtplot".
The points and ticks are rescaled with a version of the Mathematica-function
Rescale
.
The axes ticks are computed with a version of the R-function
pretty
.
TODO
DONE Plotting a list of two-element lists.
DONE Optional tick labels format specs.
DONE CLI design and implementation.
DONE Make use kebab-case for named arguments and make corresponding camel-case aliases.
DONE Multi-list plot support.
DONE Plot title.
DONE text-pareto-principle-plot
DONE text-histogram
TODO Proper respect of width and height.
- Currently, the width and height are for the plot frame -- title, axes- and tick labels are "extra."
TODO Make the axes ticks to be on the left.
It was just much easier to put them on the right.
BTW, this is probably a bug -- the width of the "total plot" is larger than the specified.
TODO Optional placement of tick values.
TODO text-plot
- Easy to implement inlined with
text-plot
, but it might give a simpler interface.
TODO text-bar-chart
References
[AAp0] Anton Antonov,
Text::Plot Raku package,
(2022),
GitHub/antononcube.
[AAp1] Anton Antonov,
Data::Generators Raku package,
(2021),
GitHub/antononcube.
[AAp2] Anton Antonov,
Data::Summarizers Raku package,
(2021),
GitHub/antononcube.
[BB1] Bjoern Bornkamp,
txtplot R package,
(CRAN),
(2020),
GitHub/bbnkmp.