Matplotlib-Simple

 view release on metacpan or  search on metacpan

lib/Matplotlib/Simple.pm  view on Meta::CPAN

		say { $args->{fh} } 'ys = []';
		my ( $min_x, $max_x ) = ( 'inf', '-inf' );
		foreach my $run ( 0 .. scalar @{ $plot->{data} } - 1 ) {
			$min_x = min( $min_x, @{ $plot->{data}[$run][0] } );
			$max_x = max( $max_x, @{ $plot->{data}[$run][0] } );
		}
		say { $args->{fh} } "base_y = np.linspace($max_x, $min_x, 101)";
		foreach my $run ( 0 .. scalar @{ $plot->{data} } - 1 ) {
			say { $args->{fh} } 'x = ['
			  . join( ',', @{ $plot->{data}[$run][0] } ) . ']';
			say { $args->{fh} } 'y = ['
			  . join( ',', @{ $plot->{data}[$run][1] } ) . ']';
			say { $args->{fh} } "ax$ax.plot(x, y, '$color', alpha=0.15)";
			say { $args->{fh} } 'y = np.interp(base_y, x, y)';
			say { $args->{fh} } 'ys.append(y)';
		}
		say { $args->{fh} } 'ys = np.array(ys)';
		say { $args->{fh} } 'mean_ys = ys.mean(axis=0)';
		say { $args->{fh} } 'std = ys.std(axis=0)';
		say { $args->{fh} } 'ys_upper = mean_ys + std';
		say { $args->{fh} } 'ys_lower = mean_ys - std';
		say { $args->{fh} } "ax$ax.plot(base_y, mean_ys, '$color')";
		say { $args->{fh} }
		"ax$ax.fill_between(base_y, ys_lower, ys_upper, color='$color', alpha=0.3)";
	} else {
	  die "$current_sub cannot take ref type \"$ref_type\" for \"data\"";
	}
}

sub print_type {
	my $str = shift;
	my $type = 'no quotes';
	if (looks_like_number($str)) { # numbers (e.g. 0.8) must not be quoted
		return 'no quotes';
	}
	if ($str =~ m/\w+\h*=\h*["'\(]/) {
		return 'no quotes';
	}
   if ($str =~ m/^\w+$/) {
   	return 'single quotes';
   } elsif ($str =~ m/^\[\h*\-?\d.+\d\h*\]$/) {
   	return 'no quotes';
   } elsif ($str =~ m/[!@#\$\%^&*\(\)\{\}\[\]\<\>,\/\-\h:;\+=\w]+$/) {
   	return 'single quotes';
   } elsif (($str =~ m/,/) && ($str !~ m/[\]\[]/)) {
   	return 'single quotes';
   }
   return $type;
}
my @all_opt;
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)";
	}
	if ( scalar @{$p} == 0 ) {
		die "$current_sub: \"p\" is empty";
	}
	# "p" supersedes the older interface; forbid mixing to avoid ambiguity
	foreach my $clash ( grep { defined $args->{$_} } ( 'plot.type', 'data', 'plots', 'add' ) ) {
		die "$current_sub: \"$clash\" cannot be combined with \"p\"";
	}
	my @plots;
	my $i = 0;
	foreach my $subplot ( @{$p} ) {
		my $ref = ref $subplot;
		if ( $ref eq 'HASH' ) {                       # one subplot, one plot
			push @plots, { %{$subplot} };             # shallow copy; don't mutate caller
		} elsif ( $ref eq 'ARRAY' ) {                 # one subplot, several overlaid plots
			if ( scalar @{$subplot} == 0 ) {
				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
	# trailing cells are pruned later by the engine.
	my $n     = scalar @plots;
	my $ncols = $args->{ncols} // $args->{ncol};
	my $nrows = $args->{nrows} // $args->{nrow};
	if ( ( not defined $ncols ) && ( not defined $nrows ) ) {
		$ncols = 1;
		$ncols++ while ( $ncols * $ncols ) < $n;             # ceil(sqrt $n), no floats
		$args->{ncols} = $ncols;
		$args->{nrows} = int( ( $n + $ncols - 1 ) / $ncols );  # ceil($n / $ncols)
	} elsif ( ( not defined $ncols ) && ( $nrows > 0 ) ) {   # only rows given
		$args->{ncols} = int( ( $n + $nrows - 1 ) / $nrows );
	} elsif ( ( not defined $nrows ) && ( $ncols > 0 ) ) {   # only cols given
		$args->{nrows} = int( ( $n + $ncols - 1 ) / $ncols );
	}
	return $args;
}
sub plt {
	my $current_sub = ( split( /::/, ( caller(0) )[3] ) )[-1]
	; # https://stackoverflow.com/questions/2559792/how-can-i-get-the-name-of-the-current-subroutine-in-perl
	# Accept BOTH calling conventions:
	#   new style:       plt( key => value, ... )
	#   back-compatible: plt({ key => value, ... })
	my $args;
	if ( ( scalar @_ == 1 ) && ( ref $_[0] eq 'HASH' ) ) {
		$args = $_[0];      # plt({ ... })
	} elsif ( ( scalar @_ % 2 ) == 0 ) {
		$args = { @_ };     # plt( ... )
	} else {
		die "$current_sub: odd number of arguments; call as $current_sub( key => value, ... ) or $current_sub({ key => value, ... })";
	}
	# fold the "p" interface into the engine's internal plot model
	normalise_p( $args, $current_sub ) if defined $args->{p};
	if ((scalar grep {$args->{$_}} ('output.file', 'show')) == 0) {
		p $args;
		die 'either "show" or "output.file" must be defined';
	}
	my @reqd_args = $args->{show} ? () : ('output.file'); # e.g. "my_image.svg"; optional if "show" is set
	my $single_example = 'plt({
	\'output.file\' => \'/tmp/gospel.word.counts.svg\',
	\'plot.type\'       => \'bar\',
	data              => {
	\'Matthew\' => 18345,
	\'Mark\'    => 11304,
	\'Luke\'    => 19482,
	\'John\'    => 15635,
	}
	});';
	my $multi_example = 'plt({
	\'output.file\'	=> \'svg/pie.svg\',
	plots             => [

lib/Matplotlib/Simple.pm  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;
      }
   }

lib/Matplotlib/Simple.pm  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:


lib/Matplotlib/Simple.pm  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.680 second using v1.01-cache-2.11-cpan-7fcb06a456a )