Matplotlib-Simple

 view release on metacpan or  search on metacpan

README.pod  view on Meta::CPAN



=head1 Multiple Plots

Having a C<plots> argument as an array lets the module know to create subplots:

 use Matplotlib::Simple 'plt';
 plt(
     'output.file'   => 'svg/pies.png',
     plots             => [
     {
             data    => {
              Russian => 106_000_000,  # Primarily European Russia
              German => 95_000_000,    # Germany, Austria, Switzerland, etc.
             },
             'plot.type' => 'pie',
             title       => 'Top Languages in Europe',
             suptitle    => 'Pie in subplots',
         },
         {
             data    => {
              Russian => 106_000_000,  # Primarily European Russia
              German => 95_000_000,    # Germany, Austria, Switzerland, etc.
             },
             'plot.type' => 'pie',
             title       => 'Top Languages in Europe',
         },
     ],
     ncols    => 2,
 );

which produces the following subplots image:


=for html
<p>
<img width="651" height="424" alt="pies" src="https://github.com/user-attachments/assets/49d3e28b-f897-4b01-9e72-38afa12fa538" />
<p>


C<bar>, C<barh>, C<boxplot>, C<hexbin>, C<hist>, C<hist2d>, C<imshow>, C<pie>, C<plot>, C<scatter>, and C<violinplot> all match the methods in matplotlib itself.

=head2 The C<p> argument

C<p> is a single, uniform way to describe one I<or> many subplots, so you no
longer need a top-level C<plot.type> (or the older C<plots> array). Each plot is a
hash, exactly like a single-plot call, and C<p> collects the subplots into one
array.

The rule is simple: B<< one element of C<p> is one subplot. >>

=over


=item * A B<hash> element is a subplot containing a single plot.


=item * An B<array of hashes> element is a single subplot whose plots are drawn on

the B<same> axes: the first hash is the base plot and the rest are
I<additions> (overlays), exactly like C<add>.

=back


The two forms may be B<mixed freely> in the same C<p>. The first (or only) hash
of a subplot supplies that subplot's axes-level options (C<title>, C<xlabel>,
C<ylabel>, C<legend>, …).

If you don't give a grid, the subplots are laid out automatically on a
near-square grid. Give C<ncol>/C<nrow> (aliases for C<ncols>/C<nrows>) to control
it; supplying only one dimension derives the other (so C<< ncols =E<gt> 1 >> stacks the
subplots in a single column), and supplying both is honored as given.

C<p> cannot be combined with C<plot.type>, C<data>, C<plots>, or C<add>.

 > Arguments are now passed as a plain list — C<plt( ... )> — though the older
 > C<plt({ ... })> form still works.

=head3 One subplot, several plots overlaid

Wrap the plots in an inner array and they all land on a single subplot (the
first is the base plot, the rest are additions):

 plt(
     p => [
         [
             {
                 data => {
                     E => [ 55, @{$x}, 160 ],
                     B => [ @{$y}, 140 ],
                 },
                 'plot.type' => 'boxplot',
                 title       => 'Single Box Plot: Specified Colors',
                 colors      => { E => 'yellow', B => 'purple' },
             },
             {
                 data => {
                     A => [ 55, @{$z} ],
                     E => [ @{$y} ],
                     B => [ 122, @{$z} ],
                 },
                 'plot.type' => 'violinplot',
                 title       => 'Single Violin Plot: Specified Colors',
                 colors      => { E => 'yellow', B => 'purple', A => 'green' },
             },
         ],
     ],
     'output.file' => '1plot.svg',    # note: no C<plot.type> needed
 );

=head3 Multiple subplots

Give each subplot as its own element. A bare hash is a one-plot subplot, so two
hashes make two subplots; with C<< ncol =E<gt> 2 >> they sit side by side:

 plt(
     p => [
         {
             data => {
                 E => [ 55, @{$x}, 160 ],
                 B => [ @{$y}, 140 ],
             },
             'plot.type' => 'boxplot',
             title       => 'Box Plot: Specified Colors',
             colors      => { E => 'yellow', B => 'purple' },
         },
         {
             data => {
                 A => [ 55, @{$z} ],
                 E => [ @{$y} ],
                 B => [ 122, @{$z} ],
             },
             'plot.type' => 'violinplot',
             title       => 'Violin Plot: Specified Colors',
             colors      => { E => 'yellow', B => 'purple', A => 'green' },
         },
     ],
     ncol          => 2,
     'output.file' => '2plots.svg',
 );

To overlay extra plots on any one subplot, make that element an array of hashes
instead of a bare hash (the first is the plot, the rest are additions). Bare
hashes and inner arrays may be intermixed in the same C<p>, for example
C<< p =E<gt> [ \%single, [ \%base, \%overlay ], \%another ] >>.

=head2 Options

C<sharex> and C<sharey> are both implemented at the plot, rather than subplot, level.  See Matplotlib's documentation for more clarity.

=head1 Color Bars (colorbars)

Colarbar args attempt to match matplotlib closely

=for html
<table>
<tbody>
<tr><td>Option</td><td>Description</td><td>Example</td></tr>
<tr><td>--------</td><td>-------</td><td>------- </td></tr>
<tr><td><code>cbdrawedges</code></td><td>Whether to draw lines at color boundaries</td><td><code>cbdrawedges => 1</code></td></tr>
<tr><td><code>cblabel</code></td><td>The label on the colorbar's long axis</td><td><code>cblabel => 1</code></td></tr>
<tr><td><code>cblocation</code></td><td>of the colorbar None or {'left', 'right', 'top', 'bottom'}</td><td></td></tr>
<tr><td><code>cborientation</code></td><td># None or {<code>vertical</code>, <code>horizontal</code>}</td><td></td></tr>
<tr><td><code>cbpad</code></td><td>pad : float, default: 0.05 if vertical, 0.15 if horizontal; Fraction of original Axes between colorbar and new image Axes</td><td></td></tr>
<tr><td><code>cb_logscale</code></td><td>Perl true (anything but 0) or false (0)</td><td></td></tr>
<tr><td><code>shared.colorbar</code></td><td>share colorbar between different plots: specify plot indices</td><td><code>'shared.colorbar' => [0,1]</code></td></tr>
</tbody>
</table>

=head1 Size/Dimensions of output file

=for html
<table>
<tbody>
<tr><td>Option</td><td>Description</td><td>Example</td></tr>
<tr><td>--------</td><td>-------</td><td>-------</td></tr>
<tr><td><code>scale</code></td><td>scale/multiply the size of the output figure</td><td><code>scale => 2.4</code></td></tr>
<tr><td><code>scalex</code></td><td>scale/multiply the x-axis only</td><td><code>scalex => 2.4</code></td></tr>
<tr><td><code>scaley</code></td><td>scale/multiply the y-axis only</td><td><code>scalex => 1.4</code></td></tr>
</tbody>
</table>

=head1 Examples/Plot Types

Consider the following helper subroutines to generate data to plot:

 sub linspace { # mostly written by Grok
    my ($start, $stop, $num, $endpoint) = @_; # endpoint means include $stop
    $num = defined $num ? int($num) : 50; # Default to 50 points
    $endpoint = defined $endpoint ? $endpoint : 1; # Default to include endpoint
    return () if $num < 0; # Return empty array for invalid num
    return ($start) if $num == 1; # Return single value if num is 1
    my (@result, $step);
 
    if ($endpoint) {
        $step = ($stop - $start) / ($num - 1) if $num > 1;
        for my $i (0 .. $num - 1) {
          $result[$i] = $start + $i * $step;
        }
   } else {
      $step = ($stop - $start) / $num;
      for my $i (0 .. $num - 1) {
         $result[$i] = $start + $i * $step;
      }
   }

README.pod  view on Meta::CPAN

 {
     'plot.type'   => 'plot',
     data          => {
         A => [ [5..9], [5..9] ],
         B => [ [5..9], [1..5] ],
     },
     'set.options' => {
         A => 'color = "red"',
         B => 'color = "blue", marker = "o"',
     },
 }

Note the pairing rule: a scalar C<set.options> goes with B<any> data shape; an
B<array> C<set.options> goes with B<array> data; a B<hash> C<set.options> goes
with B<hash> data. Mismatches (for example a hash of options with array data)
are rejected with an explanatory error.

=head3 Other options

=over


=item * C<show.legend> — on by default (C<1>); set to C<0> to suppress labels. Only the

hash form produces labels in the first place.

=item * C<key.order> — array of keys (hash form) fixing the draw/legend order; defaults

to the keys sorted alphabetically.

=item * C<logscale> — array of axes to put on a log scale, e.g. C<[ 'x', 'y' ]>.


=item * C<twinx> — draw selected series against a secondary y-axis.

=over


=item * hash data: a single key, or a hash whose keys are the series to twin;


=item * array data: an integer index, or an array of indices.


=back


=item * C<twinx.args> — a hash keyed by data key (hash form) or index (array form);

each value is a hash of axis options (e.g. C<ylabel>, C<set_ylim>) applied to
that twin axis.

=back


Common axes options such as C<title>, C<xlabel>, C<ylabel>, and C<legend> are
accepted here too, exactly as for the other plot types.

A C<plot> spec is an ordinary plot hash, so it can be dropped straight into the
L<#the-p-argument> argument — on its own for a single subplot, or alongside
other hashes to overlay or to fill a grid of subplots.

=head3 single, simple

data can be given as a hash, where the hash key is the label:

 plt(
     fh => $fh,
     execute           => 0,
     'output.file' => 'output.images/plot.single.png',
     data              => {
         'sin(x)' => [
             [@x],                     # x
             [ map { sin($_) } @x ]    # y
         ],
         'cos(x)' => [
             [@x],                     # x
             [ map { cos($_) } @x ]    # y
         ],
     },
     'plot.type' => 'plot',
     title       => 'simple plot',
     set_xticks  =>
     "[-2 * $pi, -3 * $pi / 2, -$pi, -$pi / 2, 0, $pi / 2, $pi, 3 * $pi / 2, 2 * $pi"
      . '], [r\'$-2\pi$\', r\'$-3\pi/2$\', r\'$-\pi$\', r\'$-\pi/2$\', r\'$0$\', r\'$\pi/2$\', r\'$\pi$\', r\'$3\pi/2$\', r\'$2\pi$\']',
     'set.options' => {    # set options overrides global settings
         'sin(x)' => 'color="blue", linewidth=2',
         'cos(x)' => 'color="red",  linewidth=2'
     }
 );

or as an array of arrays:

 plt(
     fh => $fh,
     execute           => 0,
     'output.file' => 'output.images/plot.single.arr.png',
     data              => [
         [
             [@x],                     # x
             [ map { sin($_) } @x ]    # y
         ],
         [
             [@x],                     # x
             [ map { cos($_) } @x ]    # y
         ],
     ],
     'plot.type' => 'plot',
     title       => 'simple plot',
     set_xticks  =>
     "[-2 * $pi, -3 * $pi / 2, -$pi, -$pi / 2, 0, $pi / 2, $pi, 3 * $pi / 2, 2 * $pi"
      . '], [r\'$-2\pi$\', r\'$-3\pi/2$\', r\'$-\pi$\', r\'$-\pi/2$\', r\'$0$\', r\'$\pi/2$\', r\'$\pi$\', r\'$3\pi/2$\', r\'$2\pi$\']',
     'set.options' => [    # set options overrides global settings; indices match data array
         'color="blue", linewidth=2, label = "sin(x)"', # labels aren't added automatically when using array here
         'color="red",  linewidth=2, label = "cos(x)"'
     ],
 );

both of which make the following "plot" plot:


README.pod  view on Meta::CPAN

Better warning when color key isn't defined for C<scatter>

When giving two hash of hashes for a barplot, if one second key is defined in one subplot, but not the other, that subkey is initialized to 0.

=head3 Cross-platform support

The module now should run on Windows in addition to Linux and macOS.

The generated Python script is written to the system temporary directory (via C<< File::Spec-E<gt>tmpdir() >>) instead of a hard-coded C</tmp>, which does not exist on Windows.

The Python interpreter is now discovered automatically by probing, in order, C<python3>, C<python>, and the Windows C<py> launcher, accepting the first that reports Python 3. This fixes Windows, where the interpreter is typically named C<python> (not...

The Python script is now executed with the list form of C<system> rather than a single shell string, so script paths containing spaces (common on Windows, e.g. C<C:\Users\First Last\AppData\Local\Temp>) no longer break execution.

The C<Creator> metadata embedded in the output file is now passed through C<write_data> (base64), so Windows paths containing backslashes no longer produce invalid escape sequences (e.g. C<\U> in C<C:\Users>) in the generated Python string literal.

On Windows, C<Win32::Console::ANSI> is loaded if available (it is optional, not a hard dependency) so colored status messages render on legacy consoles.

=head3 Crashes / generated-code fixes

C<violinplot> is now a callable wrapper; it was exported and dispatched but never defined, so calling it died with "Undefined subroutine".

C<hist> with an array of C<bins> no longer emits a stray double-quote (e.g. C<[0,2,4"]>) that caused a Python C<SyntaxError>.

C<hexbin> and C<hist2d> no longer pass C<cblabel> twice (once inside the option string and again as C<< label =E<gt> ... >>), which previously caused a duplicate-keyword C<SyntaxError>.

C<scatter> with a scalar C<set.options> no longer emits a doubled comma (C<scatter(x, y, , ...)>), which was a C<SyntaxError>.

Stacked C<barh> now uses the C<left> keyword for stacking instead of C<bottom>, which collided with C<barh>'s own C<bottom> (y-position) parameter and raised "got multiple values for keyword argument 'bottom'".

C<colored_table> with C<cb_logscale> together with C<cb_min>/C<cb_max> no longer emits C<LogNorm(, vmin=...)> with a leading comma (a C<SyntaxError>).

C<plot> with a hash of data and a scalar C<set.options> no longer crashes by dereferencing a string as a hash under C<strict refs>.

C<plot> with a hash of data now accepts a scalar C<twinx> naming a data key (e.g. C<< twinx =E<gt> 'pressure' >>); previously the value was wrongly required to be a digit string, making key-named C<twinx> impossible.

Grouped bar plots with a single scalar C<color> (e.g. C<< color =E<gt> 'green' >>) no longer crash trying to dereference the string as an array; the color is applied to all series.

=head3 Incorrect-output fixes

C<colored_table> no longer clobbers asymmetric data: filling undefined cells with C<np.nan> previously also overwrote the mirror cell, destroying defined values (if C<< A-E<gt>B >> was defined but C<< B-E<gt>A >> was not, both became C<NaN>).

C<colored_table> now honors C<cb_min> and C<cb_max>; they were read from the wrong hash (C<$args> instead of the plot options) and so were silently ignored.

C<colored_table> now honors the C<cmap> option; the color map and C<set_bad> color were hard-coded to C<gist_rainbow> regardless of the C<cmap> given. The colormap is copied before calling C<set_bad>, as registered colormaps are immutable in current ...

C<colored_table> default row labels now mirror the column labels, matching the matrix that is actually built; with asymmetric data the old default could produce a row-label count mismatch ("'rowLabels' must be of length N").

C<scatter> (single set, three keys) now honors the C<cmap> option instead of always using C<gist_rainbow>.

C<scatter> now validates undefined values in I<both> coordinate keys; the undefined-data check previously inspected only the first key.

Grouped, non-stacked bar widths are now divided by the number of bar series (plus one), not by a constant; the old divisor came from a hash that always held exactly one key, so groups with more than a few series overlapped their neighbors.

The C<wide> plot no longer clamps the upper standard-deviation band at C<1>; that clamp assumed data in the range C<[0, 1]> and clipped ordinary data (the documented example reaches roughly C<1.9>).

Numeric arguments to C<plt> methods (e.g. C<< margins =E<gt> 0.2 >>) are no longer quoted into strings; C<print_type> now recognizes numbers.

C<plt.show()> is now emitted after C<plt.savefig()> (and only once), so using C<show> no longer writes the file only after the interactive window is closed; C<output.file> is no longer required when C<show> is requested.

The C<add> overlay's C<plot.type> now correctly falls back to the parent plot's type when omitted, in both single- and multi-plot calls; the fallback was previously unreachable dead code, and an undefined type could be dispatched on.

=head3 Cleanups

Removed corrupted entries from the method whitelists (C<'set_mouseover( '> and a leading-space C<' FixedFormatter'>) that made those options unusable.

Removed a stray default applied to the wrong hash in C<violin>, two empty dead C<if> blocks, and a duplicated C<die>.

=head2 0.27

Better warnings for undefined data in C<scatter>

C<color_key> didn't work properly for multiple sets of data in C<scatter>, which has now been fixed

=head2 0.26

C<ncol> & C<nrow> are synonymous with C<ncols> and C<nrows> respectively; testing now reflects these two specifically numeric options

no longer exports Data::Printer and Devel::Confess with the module, but is still used inside the module

'show.legend' option added to "hist", which is automatically turned off if there is only 1 group

"add" group is no longer deleted

"boxplot", "hist", and "violin" can take a single array, simplifying calls without requiring useless single keys when calling a single distribution

C<cb_min> and C<cb_max> now work for colored_table

"write_data" is no longer used in hist, as it prints numbers as strings (python3's types are a headache)

Instead, all values are checked in hist for being numeric before being sent to "write_data"

re-use undefined error array in hist_helper (slightly less RAM use)

=head2 0.25

re-used error array in scatter_helper

better warnings for undefined values in multiple-set scatterplots

fixed bug in scatterplot, where different sets would have the same label

"logscale" now available with "boxplot, "hist", "plot", "scatter"

$VERSION now prints with metadata for SVG output files, which required minor changes to testing

slightly better warnings in plot_helper

removed duplicate check from hist2d_helper

better warnings if wrong data types are given to "add"

Fixed bug in scatterplot, where color key could repeat on axes

=head2 colorbar can now be in logscale for colored_table

=head2 ## 0.24

Newlines are now possible in key names for barplot and pie; other characters may be fixed too

@prop_cycle is only now taking RAM/valid where it's needed



( run in 0.587 second using v1.01-cache-2.11-cpan-7fcb06a456a )