Matplotlib-Simple

 view release on metacpan or  search on metacpan

README.md  view on Meta::CPAN

`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.

README.md  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 `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 |

README.md  view on Meta::CPAN

  - 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              => {

README.md  view on Meta::CPAN

`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`

README.pod  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

README.pod  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

README.pod  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              => {

README.pod  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>

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 0.838 second using v1.01-cache-2.11-cpan-7fcb06a456a )