Revision history for Matplotlib-Simple

0.313 2026-09-15 CDT

 [A "plot.type" that isn't defined]

 - A misspelled `plot.type` now says what you wrote and which of the defined types it resembles, the way a misspelled option has since 0.313: `'plot.type' => 'barr'` dies with `"barr" isn't a defined plot.type, perhaps you meant one of these defined plot types: (bar, barh)`. It is matched by the same rules as an option name, so separators are ignored (`hist_2d` finds `hist2d`, `coloredtable` finds `colored_table`), two transposed characters count as one mistake (`pye` finds `pie`), and a type reached for by part of its name is found (`box` finds `boxplot`). When nothing resembles what was typed, all fifteen types are printed.

 - A misspelled `plot.type` at a subplot no longer dies inside perl. It reached the dispatch table as an undefined code reference and came out as `Can't use an undefined value as a subroutine reference at .../Simple.pm line 2862`, which names neither the type nor the subplot; the same mistake in a single plot printed the whole arguments hash through `Data::Printer` and died `barr isn't defined`. Both now get the message above, and a subplot's says which subplot: `"violn" isn't a defined plot.type at subplot 1`.

 - The type given for an `add` graph is checked the same way, and says which graph it belongs to -- `"pye" isn't a defined plot.type for an "add" graph at subplot 1` -- rather than the previous `"pye" isn't a known plot.type at ax = 1`, which offered nothing.

 - Every subplot's type is checked before the figure is written rather than as each subplot is reached, so a bad type at the last subplot is reported without the earlier ones having been generated first.

 - The type is checked before the options are. With an unrecognised type there is no per-type option list to check the options against, so `plt` fell back to the union of every type's, and `'plot.type' => 'barr', notch => 'True'` reported neither the type nor the option: `notch` is on some type's list, so it passed, and the caller was told the type was wrong only much later, by `Data::Printer`. The message is now about the type, which is the thing that does not exist.

 [Bug fixes]

 - The subplot indices reported by "The above subplot indices are missing "plot.type"" are now the indices of the subplots that are missing one. The counter was incremented only on the subplots that had already failed the test, so three subplots with the third missing its type reported index 0.

 [Testing]

 - `t/04.options.t` covers the above: the eight misspellings above at a single plot, the three nested places a type can be given, a type resembling nothing, and a misspelled type given together with another type's option.

 - An `unlike()` in `t/04.options.t` was matching against the `Devel::Confess` stack trace as well as the message. Every frame in that trace prints its own arguments, so an option named in the call appears in `$@` whether or not the module mentioned it, and an assertion that the module did *not* name something could fail on a message that was right. The two assertions of that shape now match the message alone, as `t/03.coverage.t` has since 0.312.

 [Error messages for an option that isn't defined]

 - An option that no plot type accepts, or that this plot type does not accept, now says what you wrote and what it resembles: `xlim => '0, 20000'` at a bar chart dies with `"xlim" isn't defined for plot.type "bar", perhaps you meant one of these defined keywords: (clim, ylim, set_xlim)`. Previously the module printed the offending key, then dumped all 450-odd accepted keywords through `Data::Printer` and died with "The above args are accepted" -- a list long enough that the answer in it was no easier to find than in the documentation. The full list is still printed, but only when nothing resembles what was typed.

 - The suggestions are drawn from the list the plot type in hand actually accepts, not from the union of every type's, so `bins_` is offered `bins` at a `hist` and is not offered it at a `boxplot`, which has no `bins`. Separators are ignored when matching, so `key_order` finds `key.order` and `show_legend` finds `show.legend`; and two transposed characters count as one mistake, so `widht` finds `width`.

 - An option that is real but belongs to another plot type is named as such: `plot(..., notch => 'True')` now adds `"notch" is a defined keyword, but for plot.type boxplot`. This is the commonest way to land here -- `stacked`, `bins`, `notch` and `whiskers` are all somebody's option -- and the bare refusal read as though the documentation were wrong.

 - `plt` checks a single plot's options against that plot type's list rather than against every type's, so the refusal comes with the suggestions for the type asked for. Nothing new is refused: the helper the plot dispatches to was already checking the same hash against the same list, one step later.

 - `colored_table` was checking its own internal arguments (`fh`, `plot`, `ax`) against the option list, and never checking the caller's options at all. An option belonging to another plot type -- `notch`, say -- therefore passed `plt`, which accepts any type's option, and was then silently ignored rather than refused. `colored_table`'s options are now checked like every other plot type's.

 [`autodie` removed from the module]

 - `lib/Matplotlib/Simple.pm` no longer does `use autodie ':all'`. The two builtins it covered now check their own return values, so nothing that used to die returns false and carries on: `binmode` keeps autodie's wording, and the `system` that runs the generated script reports the same three cases autodie told apart -- failed to start, died to a signal, and a non-zero exit -- naming the script each time. `autodie` and `IPC::System::Simple` stay in the prerequisites because `t/01.all.tests.t` still uses them.

 - A generated script that fails to run now prints python's own error. autodie raised the failure from inside `capture`, so the branch that prints the captured STDOUT and STDERR was never reached and the traceback was thrown away: a script that would not parse reported only `"python3" unexpectedly returned exit value 1 at .../Capture/Tiny.pm line 382`, which says nothing about which option was at fault.

 - The generated script is now run as `system('python3', $file)` rather than `system("python3 $file")`. The one-argument form goes through the shell and splits on whitespace, so a temp directory with a space in its name -- ordinary under `C:\Users\<name>\AppData\Local\Temp` on MSWin32 -- ran python3 against a truncated path.

 [`hist` reports how tall its bars are]

 - Every subplot drawn with `plot.type => 'hist'` now says the range of its bin heights on STDOUT as it is drawn: `plot 0 hist range = [1, 12]`, over all of the sets drawn into that subplot, `plot 1` being the second subplot of the figure. The heights are matplotlib's -- this module hands it the data and the bins and is told nothing back -- so reading them off the figure by eye was the only way to have them. Nothing is passed to matplotlib to report them, and the file written to `output.file` is byte-for-byte the file that was written before.

 - A successful run's STDOUT is no longer thrown away. The generated script is run inside `capture`, and what it printed was passed on only when it *failed*: `hist2d` has printed its density range since it was added, in 0.09 (commit 651c239), and nobody has ever seen one. Both ranges now reach the terminal.

 [Packaging]

 - `IPC::System::Simple` is no longer a prerequisite. Nothing used it directly; it was pulled in by `use autodie ':all'` in `t/01.all.tests.t`, which loads it at compile time whether or not a `system()` is ever fatalised. That test calls only `open`, `close`, `mkdir` and `unlink`, all covered by `:default`, so it now asks for `:default` and the module -- which is not core -- no longer has to be installed to use or test this distribution.

 - `Test::More` and `Test::Exception` moved from the runtime prerequisites to the test prerequisites, where they belong: both are used only by `t/`, and neither is loaded by `lib/Matplotlib/Simple.pm`. Installing the module no longer asks for them. `[Prereqs]` in `dist.ini` is now exactly what the module loads, which was checked by hiding every test-only prerequisite from `@INC` and rendering a plot.

 - `autodie` moved from the runtime prerequisites to the test prerequisites, which is what it now is: `lib/Matplotlib/Simple.pm` stopped using it in this release. It is still declared rather than assumed, because it is core only from perl 5.010001 and this distribution supports 5.010.

 - `META.yml` and `META.json` now carry a `provides` field naming the one module the distribution installs, `Matplotlib::Simple`, and the version it installs. Both files shipped without one, which is the CPANTS `meta_yml_has_provides` failure: a tool that wants to know what this tarball claims to index had to unpack it and scan `lib/` to find out. `dist.ini` gained `[MetaProvides::Package]`, which fills the field in from the gathered `.pm` files.

 [Security policy]

 - The distribution now ships a `SECURITY.md`, generated by `Software::Security::Policy::Individual` and following the CPAN Security Group's guidelines, saying where to report a vulnerability and what is supported. `security.policy.pl` in the git repository regenerates it and records why each value in it was chosen; it is not part of the tarball.

0.3121 2026-09-07 CDT

 [Portability]

 - The generated Python script is now written to `File::Spec->tmpdir` rather than a hard-coded `/tmp`. MSWin32 has no `/tmp`, so every call to `plt` died there with "Error in tempfile() using template \tmp\XXXXXXXXXX.py: Parent directory (\tmp\) does not exist", taking 129 of 163 subtests in `t/04.options.t` with it (CPAN Testers FAIL for 0.312, perl 5.42.2 on Strawberry/Win10). On unix the file still lands in `/tmp`, as before. `t/01.all.tests.t` writes its output images to the same directory for the same reason.

 - The `Creator` metadata written into each image is now emitted as a properly escaped Python string literal. It embeds `getcwd()` and the module's own path, which are backslash paths on MSWin32: pasted raw into the literal, `C:\build\...` read `\b` as a backspace and `\c` as an invalid escape (a SyntaxWarning in Python 3.12, a SyntaxError from 3.15), and an apostrophe anywhere in the path closed the literal early.

0.312 2026-09-04 CDT

 [`wide` summaries]

 - `wide` now sorts each run by x before interpolating it. `np.interp` requires its sample points in ascending order, and nothing enforced that, so a run whose x descended (or was otherwise unordered) was summarised as a flat line while the faint raw run underneath drew correctly — a wrong picture that looked authoritative.

 - A run now counts towards the mean and the ribbon only between its own first and last x. `np.interp` holds the end values flat outside the sample points, so a run that stopped short of the group's x range used to contribute an invented horizontal line to both the mean and the standard deviation; a run ending halfway could double the apparent spread over the second half. The summary at each point is now taken over the runs that actually reach it, which is what the documented support for "runs of different lengths, or sampled at different x values" always promised.

 - `wide` now rejects malformed `data` with a message naming the group and the run, instead of dying inside the writer with `Can't use string ("0") as an ARRAY ref`. Passing `plot`'s single `[ \@x, \@y ]` pair where a group of runs belongs, an empty group, mismatched x and y lengths, and non-numeric values are each reported. Giving `color` as a hash for array data, or as a single color for hash data, is also caught rather than dying on a bad dereference.

 [Labels taken from data keys]

 - Text that comes from the data — legend labels, colorbar labels, and the `xlabel`/`ylabel` that `scatter`, `hexbin` and `hist2d` default from their keys — is now written as a properly escaped Python string literal. A key containing an apostrophe, such as `Farmer's`, previously closed the literal early and the generated script would not parse. Because backslashes are escaped too, a mathtext label such as `$\alpha$` now reaches matplotlib as written instead of being read as a Python escape. Title and label text passed by the caller is unchanged: it is still Python syntax, so a comma or a quote in it must still be quoted by the caller.

 [Testing]

 - The error-path assertions in the test suite were passing regardless of what the code did. `Devel::Confess` appends a stack trace in which every frame prints its own arguments, one of which is the regular expression handed to `throws_ok`/`dies_like`, so the exception always contained a copy of the pattern it was being matched against. Both helpers now match the exception's message alone. All the assertions they guard still pass, so the module's messages were correct — only the harness was blind.

 - Regression tests were added for the fixes above: the `wide` summary is checked numerically (three runs of the same straight line, entered ascending, descending, and covering only half the range, must summarise to that line with a zero-width ribbon), along with the six `wide` error paths and the two guards in the generated Python; and the generated-Python parser gate now covers an apostrophe in a data key for `wide`, `plot`, `hist`, `scatter` and `hist2d`.

 [Documentation]

 - The `wide` section of the README now shows the three figures its examples produce. They are written by `wide.example.pl` in the git repository, which runs the documented code with a fixed seed so the committed images can be regenerated.

0.311 2026-07-27 CDT

 - Improved README and testing, bug fixes

 - Back-compatible to Perl-5.10, which the 0.31 broke

0.31 2026-07-25 CDT

 - Removed `Term::ANSIColor` as dependency

 - added `venn_proportional_area` as a plot helper

0.301 Unknown Release Date

 - Fixes for changes introduced in 0.30 for CPAN testers: https://www.cpantesters.org/cpan/report/143e86c6-77fa-11f1-b73a-21df6d8775ea

 - Removed files from build directory to shrink tarball

0.30 Unknown Release Date

 - non-ASCII key names (e.g. `ρ`, `τ`) no longer crash the writer. The generated-Python filehandle is now given a UTF-8 encoding layer, fixing a fatal "Wide character in say" that occurred under the module's strict-fatal warnings; the layer is added only when not already present, so a caller-supplied filehandle is never double-encoded.

 - `p` option: a flat array of subplots where one element is one subplot — a hash is a single-plot subplot, and an array of hashes is one subplot with the plots overlaid on the same axes (first hash is the base plot, the rest are additions). The two forms may be mixed in the same `p`. When no grid is given the subplots are laid out on an auto-sized near-square grid; giving only `ncol`/`nrow` (or `ncols`/`nrows`) derives the other dimension.

0.29 Unknown Release Date

 - addition of the `p` option

 - removal of SHA testing; changes in Matplotlib version 3.11 mean that SHA sums aren't compatible across different versions of Matplotlib

 - arguments can now be given as a flat hash

0.28 Unknown Release Date

 - colorbar options now work better in `scatter`.

 - 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 `python3`), and correctly rejects the Microsoft Store `python3` stub and any Python 2. Set the `MPLS_PYTHON` (or `PYTHON`) environment variable to override the interpreter with a specific name or full path.

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

 - `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 Unknown Release Date

 - 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 Unknown Release Date

 - `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 Unknown Release Date

 - 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 Unknown Release Date
 - 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

 - new dependencies in JSON::MaybeXS and MIME::Base64 to prevent errors in key names

 - slight improvement in violinplot: "print" changed to "say" (1 less concatenation)

 - dynamic method wrappers are used, which save ~120 lines of code

 - re-used error array in "plt" to save RAM

 - better warning for non-File::Temp objects

 - more tests for wrapper subroutines

 - duplicate check removed from hexbin_helper

 - removed whiskers option from boxplot_helper, which didn't work the way that I thought that it did

 - removed shebang, which isn't necessary in .pm files

 - hist2d was missing an option for logscale on the axes, which it now has

0.23 Unknown Release Date

 - colors for bar plots can be defined by hashes; e.g. colors => {A => 'red', B => 'green'}, etc

0.22 Unknown Release Date

 - minor under-the-hood changes; "execute" subroutine, which was only called once, is now built into "plt" to save a function call; execution should be slightly faster/more efficient

0.21 Unknown Release Date

 - "show" now works; files are still output if specified

0.20 Unknown Release Date
 - better warnings for incomplete data in "plot"
   "plot" can plot with "twinx" when data is given in array or hash form
   "tick_params" is removed from plt methods
   fewer "my" for error arrays, using empty arrays from earlier; should increase efficiency slightly
   added tests for twinx in plot for both array and hash variants

