Text::Plot
Raku Text::Plot
This repository has a Raku package for textual (terminal) plots.
Here is the list of functions:
DONE
text-list-plotDONE
text-pareto-principle-plotDONE
text-histogramTODO
text-plotTODO
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:
TODO
text-box-plot
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::PlotFrom GitHub:
zef install https://github.com/antononcube/Raku-Text-Plot.gitUsage 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.00Plot 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-pointsSmallish 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.00Plot 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.00Here 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);# +-------------------------------+------------------------------+
# | x | y |
# +-------------------------------+------------------------------+
# | Min => -0.6878061217490536 | Min => 5.375451235309257 |
# | 1st-Qu => 2.296842587707644 | 1st-Qu => 10.751125832775537 |
# | Mean => 3.68142428875546 | Mean => 12.714247111365054 |
# | Median => 3.786163291984308 | Median => 12.766102933074883 |
# | 3rd-Qu => 5.154324814056429 | 3rd-Qu => 14.08687917277585 |
# | Max => 9.713071001209055 | Max => 20.1245632362308 |
# +-------------------------------+------------------------------+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.00Remark: 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.00Remark: 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.00Here 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.95
# +---+-----------+-----------+-----------+------------+-----+
# | |
# + ****** + 1.00
# | ****** |
# + ******* + 0.80
# | ******* |
# + ****** + 0.60
# | ******* |
# + ****** + 0.40
# | ******* |
# + ***** + 0.20
# | *** |
# | ** |
# + + 0.00
# +---+-----------+-----------+-----------+------------+-----+
# 0.00 100.00 200.00 300.00 400.00text-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# 500Here is a histogram with counts:
text-histogram(@data, 30, type => 'count', :filled, point-char => <* *>);# +-------+---------+--------+--------+--------+---------+---+
# | |
# | * * * |
# + * * * + 40.00
# | * * * * * |
# + * ** * * * * + 30.00
# | * * ** * * ** * |
# | ** * ** * * ** * |
# + * * ** * ** * * ** * + 20.00
# | * * * ** * ** * * ** * * * |
# + * ** * * ** * ** * * ** * ** * * + 10.00
# | * * * ** * * ** * ** * * ** * ** * * ** * * |
# + ** * * * * ** * * ** * ** * * ** * ** * * ** * * ** + 0.00
# | |
# +-------+---------+--------+--------+--------+---------+---+
# 0.00 2.00 4.00 6.00 8.00 10.00Here is a histogram with density function estimate:
text-histogram(@data, 30, type => 'cdf', height => 20, :filled);# +-------+---------+--------+--------+--------+---------+---+
# | |
# + □□ □ □ □□ + 1.00
# | □ □ □ ** * * ** |
# | □ □* * * ** * * ** |
# + □ * ** * * ** * * ** + 0.80
# | □* * ** * * ** * * ** |
# | □ ** * ** * * ** * * ** |
# + * ** * ** * * ** * * ** + 0.60
# | □ * ** * ** * * ** * * ** |
# | □ * * ** * ** * * ** * * ** |
# + □* * * ** * ** * * ** * * ** + 0.40
# | □ ** * * ** * ** * * ** * * ** |
# | □ * ** * * ** * ** * * ** * * ** |
# + □* * ** * * ** * ** * * ** * * ** + 0.20
# | □ □ ** * ** * * ** * ** * * ** * * ** |
# | □ □□ * * ** * ** * * ** * ** * * ** * * ** |
# + □□ □ □ □ * ** * * ** * ** * * ** * ** * * ** * * ** + 0.00
# | |
# +-------+---------+--------+--------+--------+---------+---+
# 0.00 2.00 4.00 6.00 8.00 10.00Remark: 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-plotRemark: 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-plotDONE
text-histogramTODO 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-plotEasy 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.