Revision history for Matplotlib-Simple

0.314 2026-09-16 CDT

 [Text that is data is now escaped]

 - A data key holding a double quote no longer writes a python syntax error. Keys, tick labels, table row and column headings and the members of a venn set were written into double-quoted python literals by hand, so `data => { 'say "hi"' => [1,2,3] }` emitted `ax0.set_xticks([1], ["say "hi" (3)"])` and the script would not parse. Every one of them goes through the same escaping the legend labels already used.

 - A data key holding a backslash is no longer silently mangled. This was the worse half of the same bug, because the script still parsed: a key of `$\alpha$` -- mathtext, and the reason the escaping routine exists -- reached matplotlib as `$<BEL>lpha$`, since python reads the `\a` of a double-quoted literal as a bell character. The label drawn is now the label given.

 - `imshow`'s `cblabel` is quoted like every other colorbar label. `cblabel => "Farmer's yield"` closed the literal early and left an unparseable script; `hist2d`, `hexbin`, `colored_table` and `scatter` had all quoted theirs for releases.

 [Options that were accepted and then ignored]

 - `'colorbar.on' => 0` now suppresses a `hexbin` or `hist2d` colorbar, which it never has: both read the option only when deciding where to attach a *shared* colorbar and drew their own regardless. A figure sharing one colorbar across subplots therefore drew the shared colorbar and each subplot's own, since `plt` turns the others off through this same option. `show.colorbar` is unchanged and remains a `hist2d` synonym.

 - `colored_table`'s `'colorbar.on' => 0` is no longer overridden by passing `cblabel`, which used to draw the colorbar anyway -- documented rather than fixed, though nobody labels a colorbar they have just turned off.

 - `colored_table`'s `default_undefined` replaces the cells that have no value, as its name says. The line that would have used it had been commented out, so the cells stayed `np.nan` whatever was passed. A cell given a value this way counts towards the color scale like any other, so `undef.color` no longer applies to it.

 - `hist` accepts an array of bin edges per set, e.g. `bins => { A => [0,2,4,6,8] }`, which emitted `bins = ARRAY(0x55f0...)` and died as a `NameError` when python ran. The whole-plot `bins` has always taken an array.

 - `bar`/`barh` colors given as a hash now color a hash-of-hashes grouped plot, keyed by the inner keys that name the series. Given for that shape the hash was dropped without a word. A hash of arrays has no series names to key it by and says so.

 - `twinx` given the wrong shape for its data -- an array of indices against a hash of sets, or a hash against an array of lines -- is an error rather than a silent no-op that drew no second axis.

 [Data that cannot be drawn is refused, naming what is wrong]

 - A non-numeric `bar`, `barh` or `pie` value dies naming the key. It used to be written into the script as a bare python name: `data => { A => 'abc' }` emitted `vals = [abc,2]`, and the caller read a `NameError` about a temporary file. `hist`, `boxplot`, `plot` and `hist2d` have always checked.

 - A grouped `bar`/`barh` whose keys hold different numbers of values dies naming the keys and their lengths, rather than handing matplotlib a series with more bar positions than bars.

 - An ordering option -- `key.order`, `scatter`'s `keys`, `colored_table`'s `col.labels` -- that names a key `data` does not have dies naming the key and the option. Every one of them used to reach the writer as an undefined value and die as `Use of uninitialized value in join or string`.

 - `logscale` is checked properly. The test only ever looked at one character, so `logscale => ['xy']` emitted `ax0.set_xyscale("log")` and `logscale => ['']` emitted `ax0.set_scale("log")`, neither of which matplotlib has; a scalar `logscale => 'y'` died inside perl as `Can't use string ("y") as an ARRAY ref`. All three now say what is wrong, in one place, for the seven plot types that take the option. `scatter` had no check at all.

 - A `violin` group left with nothing in it after the undefined and non-numeric values are dropped dies naming the group, instead of `Use of uninitialized value in division` from the line that averages it.

 - `imshow` with no data, and `colored_table` with no numeric cell, are refused. Their color range stayed at the strings it starts from and emitted `vmin = inf` and `plt.Normalize(inf, -inf)`, a `NameError` in python that says nothing about the data.

 - `'shared.colorbar' => []`, and entries in it that are not subplot indices, are refused rather than dying as `Use of uninitialized value $max_subplot_idx in numeric gt`.

 [Bug fixes]

 - A single plot no longer emits its figure-wide and pyplot-wide options twice. Both the per-plot pass and the figure pass walked the same hash, so `suptitle` was written twice and the second pass ran its own quoting rules over text the first had already quoted: the documented workaround was to use double quotes, since `suptitle => "'a, b'"` came out as `plt.suptitle(''a, b'')`. Text that already carries a quote of its own is now left alone, so both forms work, and options such as `xscale => 'log'` -- which the first pass emitted unquoted, as `plt.xscale(log)` -- are written once and correctly.

 - `imshow` draws a `stringmap` of one category, and of more than ten. One category divided by zero while placing the colorbar ticks (`Illegal division by zero`); an eleventh ran off the end of the ten-color property cycle and died as `Use of uninitialized value in join or string`. Past ten categories the colors come from a colormap resampled to the number of categories. Two categories used to put both ticks in the same place; each tick now sits in the middle of its own band.

 - A `colored_table` with a `title` draws one instead of dying. It wrote `ax0.title('..')` of its own, on top of the `ax0.set_title('..')` that `plt` writes for every plot type, and `title` is a `Text` attribute of the axes rather than a method: the script died with `TypeError: 'Text' object is not callable`.

 - `boxplot` and `violin` no longer edit the caller's data. Both drop the undefined (and, for `violin`, non-numeric) elements before plotting and wrote the shortened list back into the array that was passed in, so an array handed to `violin` came back shorter than it was written.

 [Documentation]

 - `pie` is documented as accepting `key.order`, which it has always read: the documentation said the wedge order "cannot be overridden" and that the option was not accepted.

 - The quoting section no longer advises double quotes as a way round the duplicate `suptitle`, which is fixed; the `hist2d` options no longer say `show.colorbar` is the only thing that suppresses its colorbar; `colored_table` documents `default_undefined`; and `bar`'s `color` says which keys a color hash is keyed by.

 [Testing]

 - `t/06.escaping.and.validation.t` covers the above: a data key holding a double quote at every plot type that writes one, a mathtext key, an apostrophe in `imshow`'s `cblabel`, one, two and eleven `stringmap` categories, every refusal listed here, and that `boxplot` and `violin` leave the caller's arrays as they found them. Like `t/05.generated.python.t` it parses what it generates with python's own parser, and skips only that part when python3 is absent.

 - Its `dies_with` matches the exception's message, not the whole exception, for the reason `t/03.coverage.t`'s `dies_like` has carried since 0.312 and `t/04.options.t` was fixed for in 0.313: `Devel::Confess` prints every frame's arguments, one of which is the pattern being matched, so an assertion of that shape matches the pattern against a copy of itself and passes whatever the code does. Its first draft did exactly that, and ten of its assertions could not fail.

 - Every fix in this release was checked by reverting it in turn and confirming that the suite fails -- 30 reversions, 30 failures. That is what found the `dies_with` above: seven of the ten assertions it was meant to protect passed with the code they test taken out.

 - The three `TODO` cases that had come true -- `pie`'s `key.order`, `violinplot`'s `medians` and `edgecolor` -- are ordinary assertions again, and the two that this release fixes (`default_undefined`, `hexbin`'s `colorbar.on`) are no longer marked `TODO`. The `suptitle` pair in `t/05.generated.python.t` is now asserted rather than expected to fail.

 [Windows]

 - `t/01.all.tests.t` no longer writes to a hardcoded `/tmp`. Around fifty `'output.file' => '/tmp/x.svg'` paths and a `File::Temp->new(DIR => '/tmp')` were generated into it by `md2pod.pl`, which is the one thing CLAUDE.md forbids outright -- it is what took 0.312 down on the Strawberry perl smokers. It never showed up in a report because this file skips without python3 and matplotlib, which a Windows smoker rarely has, but it would have failed on any Windows box that had both. The figures are written through `outfile()`, which is `File::Spec->catfile(File::Spec->tmpdir, ...)`; on unix that is still `/tmp`, so they land where they always did.

 - `md2pod.pl` generates those paths rather than copying them, so it is what was fixed: it rewrites both of the spellings `mpl.examples.pl` uses -- `output.images/x.png` for the figures the documentation shows and `/tmp/x.svg` for the ones it does not -- into `outfile('x.svg')`, and writes the `@output_files` list as basenames mapped through the same sub. `mpl.examples.pl`'s own `File::Temp` call, which is copied into the test verbatim, now takes `File::Spec->tmpdir`.

 [Dead code]

 - `colored_table` no longer carries a `logscale` loop: `logscale` is not among the options it accepts, so the loop could never run, and both sets of ticks are emptied a few lines later anyway. The `xlim => 'set_xlim'` rename in `plt` is gone for the same reason -- no keyword list accepts `xlim`, and the documentation uses its refusal as a worked example.

 [Documentation]

 - The three figures in the `wide` section and the two in `venn_proportional_area` now appear on metacpan.org. Their `<img src>` was the repository-relative `output.images/wide.png`, which GitHub resolves against the repository but metacpan resolves against the POD's own URL: `https://metacpan.org/pod/output.images/wide.png` is a 404, so all five came out as empty grey boxes. They are now absolute `https://raw.githubusercontent.com/hhg7/MatPlotLib-Simple/main/output.images/` URLs, like every other figure in the documentation, and the two venn images carry the `width` and `height` they had always been missing.

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

