Matplotlib-Simple
view release on metacpan or search on metacpan
`p` is a single, uniform way to describe one *or* many subplots, so you no
longer need a top-level `plot.type` (or the older `plots` array). Each plot is a
hash, exactly like a single-plot call, and `p` collects the subplots into one
array.
The rule is simple: **one element of `p` is one subplot.**
- A **hash** element is a subplot containing a single plot.
- An **array of hashes** element is a single subplot whose plots are drawn on
the **same** axes: the first hash is the base plot and the rest are
*additions* (overlays), exactly like `add`.
The two forms may be **mixed freely** in the same `p`. The first (or only) hash
of a subplot supplies that subplot's axes-level options (`title`, `xlabel`,
`ylabel`, `legend`, â¦).
If you don't give a grid, the subplots are laid out automatically on a
near-square grid. Give `ncol`/`nrow` (aliases for `ncols`/`nrows`) to control
it; supplying only one dimension derives the other (so `ncols => 1` stacks the
subplots in a single column), and supplying both is honored as given.
},
'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 `p`, for example
`p => [ \%single, [ \%base, \%overlay ], \%another ]`.
## Options
`sharex` and `sharey` are both implemented at the plot, rather than subplot, level. See Matplotlib's documentation for more clarity.
# Color Bars (colorbars)
Colarbar args attempt to match matplotlib closely
| Option | Description | Example |
- array data: an integer index, or an array of indices.
- `twinx.args` â a hash keyed by data key (hash form) or index (array form);
each value is a hash of axis options (e.g. `ylabel`, `set_ylim`) applied to
that twin axis.
Common axes options such as `title`, `xlabel`, `ylabel`, and `legend` are
accepted here too, exactly as for the other plot types.
A `plot` spec is an ordinary plot hash, so it can be dropped straight into the
[`p`](#the-p-argument) argument â on its own for a single subplot, or alongside
other hashes to overlay or to fill a grid of subplots.
### 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 => {
`scatter` now validates undefined values in *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 `wide` plot no longer clamps the upper standard-deviation band at `1`; that clamp assumed data in the range `[0, 1]` and clipped ordinary data (the documented example reaches roughly `1.9`).
Numeric arguments to `plt` methods (e.g. `margins => 0.2`) are no longer quoted into strings; `print_type` now recognizes numbers.
`plt.show()` is now emitted after `plt.savefig()` (and only once), so using `show` no longer writes the file only after the interactive window is closed; `output.file` is no longer required when `show` is requested.
The `add` overlay's `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.
### Cleanups
Removed corrupted entries from the method whitelists (`'set_mouseover( '` and a leading-space `' FixedFormatter'`) that made those options unusable.
Removed a stray default applied to the wrong hash in `violin`, two empty dead `if` blocks, and a duplicated `die`.
## 0.27
Better warnings for undefined data in `scatter`
=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
},
'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
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 => {
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>
lib/Matplotlib/Simple.pm view on Meta::CPAN
foreach my $type (keys %opt) {
push @all_opt, @{ $opt{$type} };
}
sub normalise_p {
# "p" is a flat list of subplots: ONE array element == ONE subplot.
# p => [ \%h, \%h, ... ] each hash is its own subplot
# p => [ \%h, [ \%h, \%h ], \%h, ... ] an inner array of hashes is a
# single subplot holding several
# plots on the same axes (the 1st
# hash is the base plot, the rest
# are "add"itions/overlays)
# The plain-hash and inner-array forms may be mixed freely in one "p".
# Using "p" means top-level "plot.type"/"data" are not used. If no grid is
# given, the subplots are laid out on an automatically-sized near-square
# grid; pass nrow(s)/ncol(s) to override.
my ( $args, $current_sub ) = @_;
$current_sub = $current_sub // 'plt';
my $p = delete $args->{p};
if ( ref $p ne 'ARRAY' ) {
die "$current_sub: \"p\" must be an ARRAY reference (one element per subplot)";
}
lib/Matplotlib/Simple.pm view on Meta::CPAN
die "$current_sub: subplot $i in \"p\" is an empty array";
}
my @g = @{$subplot};
my $main = shift @g; # 1st hash is the base plot
foreach my $plot ( $main, @g ) {
if ( ref $plot ne 'HASH' ) {
die "$current_sub: subplot $i in \"p\": every plot must be a HASH reference";
}
}
my %plot = %{$main}; # shallow copy of the base plot
push @{ $plot{add} }, @g if scalar @g; # the rest overlay on the same axes
push @plots, \%plot;
} else {
die "$current_sub: subplot $i in \"p\" must be a HASH reference (one plot) or an ARRAY of HASH references (overlaid plots), not a \"$ref\" reference";
}
$i++;
}
$args->{plots} = \@plots;
# convenience: choose a subplot grid. With neither dimension given, use a
# near-square grid; with exactly one given (and positive), derive the other
# so e.g. "ncols => 1" stacks the subplots in a single column. Empty
lib/Matplotlib/Simple.pm view on Meta::CPAN
=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
lib/Matplotlib/Simple.pm view on Meta::CPAN
},
'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
lib/Matplotlib/Simple.pm view on Meta::CPAN
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 => {
lib/Matplotlib/Simple.pm view on Meta::CPAN
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>
t/02.interface.t view on Meta::CPAN
is( count_matches( $py, qr/\bax1\.hist\(/ ), 1, 'flat p: ax1 has 1 plot' );
is( count_matches( $py, qr/\bax2\.hist\(/ ), 1, 'flat p: ax2 has 1 plot' );
is( count_matches( $py, qr/\.hist\(/ ), 3, 'flat p: 3 plots total (one per subplot)' );
# the unused 4th cell of the 2x2 grid is pruned by the engine
like( $py, qr/ax3\.remove\(\)/, 'flat p: empty 4th cell is removed' );
}
# ----------------------------------------------------------------------------
# 2. Mixed forms -> an inner array is ONE subplot with overlaid plots.
# p => [ \%h, [ \%h, \%h ], \%h ]
# ax1 must carry TWO plot calls (the overlay); ax0 and ax2 one each.
# ----------------------------------------------------------------------------
{
my $py;
lives_ok {
$py = build_py(
{ 'plot.type' => 'hist', data => { A => [1,2,3] }, title => 'single' },
[ H('B',1,2), H('C',3,4) ], # overlaid in ONE subplot
{ 'plot.type' => 'hist', data => { D => [5,6] }, title => 'third' },
);
} 'mixed p: hash + array-of-hashes + hash builds without dying';
t/02.interface.t view on Meta::CPAN
qr/cannot be combined with/, '"data" combined with "p" dies';
throws_ok { plt({ p => [ H('A',1) ], 'plot.type' => 'hist', show => 1 }) }
qr/cannot be combined with/, '"plot.type" combined with "p" dies';
throws_ok { plt({ p => [ H('A',1) ], plots => [ H('A',1) ], show => 1 }) }
qr/cannot be combined with/, '"plots" combined with "p" dies';
throws_ok { plt({ p => [ H('A',1) ], add => [ H('A',1) ], show => 1 }) }
qr/cannot be combined with/, '"add" combined with "p" dies';
# ----------------------------------------------------------------------------
# 7. Render layer: drive matplotlib end-to-end and confirm the mixed figure
# yields a well-formed SVG with exactly three Axes (the overlay shares one).
# Skipped unless matplotlib >= 3.10.
# ----------------------------------------------------------------------------
SKIP: {
skip 'matplotlib >= 3.10 not available for the render layer', 4
unless $mpl_available;
my $dir = tempdir( CLEANUP => 1 );
my $svg = File::Spec->catfile( $dir, 'condon.p_interface.svg' );
my ( $out, $err, $py );
lives_ok {
( $out, $err, $py ) = capture {
plt({
p => [
H('A',1,2,2,3,3,3,4),
[ H('B',1,1,2,2,3), H('C',2,3,3,4,4,5) ], # overlay -> one subplot
H('D',5,5,6,6,6,7),
],
'output.file' => $svg,
execute => 1,
});
};
} 'mixed p figure renders to SVG without dying';
push @tmp_to_unlink, $py if defined $py;
ok( -e $svg, 'SVG output file was created' );
ok( -s $svg, 'SVG output file is non-empty' );
my $svg_text = slurp($svg);
my $axes_seen = count_matches( $svg_text, qr/id="axes_/ );
is( $axes_seen, 3, 'exactly 3 Axes rendered (overlay collapsed into one subplot)' );
}
done_testing();
t/03.coverage.t view on Meta::CPAN
);
# logscale on both axes
like(
gen_py( 'plot.type' => 'scatter', data => { A => [@xw], B => [@yv], Z => [@cx] }, color_key => 'Z', logscale => [ 'x', 'y' ], 'output.file' => outfile('logscale.svg') ),
qr/set_[xy]scale\(\s*["']log["']/, 'logscale => [x,y] sets a log scale'
);
}
# ============================================================================
# 7. add-overlays on a single plot, and twinx.
# ============================================================================
{
# scatter with an overlaid line via "add"; the overlay draws on the same axis
my $py = gen_py(
'plot.type' => 'scatter',
data => { A => [@xw], B => [@yv], Z => [@cx] },
color_key => 'Z',
add => [
{ 'plot.type' => 'plot', 'show.legend' => 0, data => { over => [ [@xw], [@yv] ] } },
],
'output.file' => outfile('add.svg'),
);
like( $py, qr/\.scatter\(/, 'add: base scatter present' );
t/03.coverage.t view on Meta::CPAN
# ============================================================================
{
# flat: two hashes => two subplots (auto grid)
my $flat = gen_py(
p => [ { 'plot.type' => 'hist', data => { A => [@g1] } }, { 'plot.type' => 'hist', data => { B => [@g2] } } ],
'output.file' => outfile('p.flat.svg'),
);
like( $flat, qr/^ax0\.hist\(/m, 'p flat: subplot 0 on ax0' );
like( $flat, qr/^ax1\.hist\(/m, 'p flat: subplot 1 on ax1' );
# mixed: an inner array is one subplot with overlays
my $mixed = gen_py(
p => [
{ 'plot.type' => 'hist', data => { A => [@g1] } },
[ { 'plot.type' => 'hist', data => { B => [@g2] } }, { 'plot.type' => 'hist', data => { C => [@g1] } } ],
],
ncols => 1,
'output.file' => outfile('p.mixed.svg'),
);
is( count_matches( $mixed, qr/^ax1\.hist\(/m ), 2, 'p mixed: inner array overlays two plots on ax1' );
like( $mixed, qr/plt\.subplots\(\s*2\s*,\s*1\b/, 'p mixed: ncols=1 derives a 2x1 grid' );
}
# ============================================================================
# 10. Error branches specific to the helpers.
# ============================================================================
dies_like( sub { plt( 'plot.type' => 'nope', data => { A => 1 }, 'output.file' => outfile('e.svg') ) },
qr/isn't defined|isn't a known plot\.type/, 'unknown plot.type dies' );
dies_like( sub { plt( 'plot.type' => 'imshow', data => { A => [ 0, 1 ], B => [ 0, 3 ] }, 'output.file' => outfile('e.svg') ) },
( run in 3.802 seconds using v1.01-cache-2.11-cpan-7fcb06a456a )