Matplotlib-Simple

 view release on metacpan or  search on metacpan

README.md  view on Meta::CPAN

    plt(
	   'output.file'     => '/tmp/gospel.word.counts.png',
	   'plot.type'       => 'bar',
	   data              => {
		  Matthew => 18345,
		  Mark    => 11304,
		  Luke    => 19482,
		  John    => 15635,
	   }
    );

<img width="651" height="491" alt="gospel word counts" src="https://github.com/user-attachments/assets/a008dece-2e34-47bf-af0f-8603709f7d52" />

# Multiple Plots

Having a `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:

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

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

## The `p` argument

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

`p` cannot be combined with `plot.type`, `data`, `plots`, or `add`.

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

### 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 `plot.type` needed
    );

### Multiple subplots

Give each subplot as its own element. A bare hash is a one-plot subplot, so two
hashes make two subplots; with `ncol => 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 `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 |
| -------- | ------- | ------- 
|`cbdrawedges` | Whether to draw lines at color boundaries | `cbdrawedges => 1`|
|`cblabel`      | The label on the colorbar's long axis | `cblabel => 1` |
|`cblocation`   |  of the colorbar None or {'left', 'right', 'top', 'bottom'} | |
|`cborientation` | # None or {`vertical`, `horizontal`} |
|`cbpad`        | pad : float, default: 0.05 if vertical, 0.15 if horizontal; Fraction of original Axes between colorbar and new image Axes
|`cb_logscale`  | Perl true (anything but 0) or false (0)| |
|`shared.colorbar` | share colorbar between different plots: specify plot indices | `'shared.colorbar' => [0,1]`|

# Size/Dimensions of output file

| Option | Description | Example |
| -------- | ------- | ------- |
|`scale`  | scale/multiply the size of the output figure | `scale => 2.4`|
|`scalex` | scale/multiply the x-axis only | `scalex => 2.4` |
|`scaley` | scale/multiply the y-axis only | `scalex => 1.4` |

# 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;
	     }
	  }
	   return @result;
    }
    
    sub generate_normal_dist {
    	my ($mean, $std_dev, $size) = @_;
    	$size = defined $size ? int $size : 100; # default to 100 points
    	my @numbers;
    	for (1 .. int($size / 2) + 1) {# Box-Muller transform
    		my $u1 = rand();
    		my $u2 = rand();

README.md  view on Meta::CPAN

            [ [5..9], [1..5] ],
        ],
        'set.options' => 'linewidth = 2',    # both lines
    }

**An array sets options per line (positional).** With array data, give one
string per line; entry `i` styles line `i`. You may supply fewer entries than
lines, but not more:

    {
        'plot.type'   => 'plot',
        data          => [
            [ [5..9], [5..9] ],
            [ [5..9], [1..5] ],
        ],
        'set.options' => [
            'color = "red"',
            'color = "blue", linestyle = "--"',
        ],
    }

**A hash sets options per key.** With hash data, key the options by the same
data keys (any key may be omitted):

    {
        '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 `set.options` goes with **any** data shape; an
**array** `set.options` goes with **array** data; a **hash** `set.options` goes
with **hash** data. Mismatches (for example a hash of options with array data)
are rejected with an explanatory error.

### Other options

- `show.legend` — on by default (`1`); set to `0` to suppress labels. Only the
  hash form produces labels in the first place.
- `key.order` — array of keys (hash form) fixing the draw/legend order; defaults
  to the keys sorted alphabetically.
- `logscale` — array of axes to put on a log scale, e.g. `[ 'x', 'y' ]`.
- `twinx` — draw selected series against a secondary y-axis.
  - hash data: a single key, or a hash whose keys are the series to twin;
  - 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              => {
    		'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:

<img width="651" height="491" alt="plot single" src="https://github.com/user-attachments/assets/6cbd6aad-c464-4703-b962-b420ec08bb66" />

README.md  view on Meta::CPAN

Better warning when color key isn't defined for `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.

### 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 `File::Spec->tmpdir()`) instead of a hard-coded `/tmp`, which does not exist on Windows.

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

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

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

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

### Crashes / generated-code fixes

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

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

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

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

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

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

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

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

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

### Incorrect-output fixes

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

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

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

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

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

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

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

## 0.26

`ncol` & `nrow` are synonymous with `ncols` and `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

`cb_min` and `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)

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

colorbar can now be in logscale for colored_table
---
## 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.958 second using v1.01-cache-2.11-cpan-7fcb06a456a )