=encoding utf8

=head1 Synopsis

Take a data structure in Perl, and automatically write a Python3 script using matplotlib to generate an image.  The Python3 script is saved in C</tmp>, to be edited at the user's discretion.
Requires python3 and matplotlib installations.

=head1 Single Plots

Simplest use case:

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

A nore complete (and slightly faster execution):

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


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


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

=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;
      }
   }
    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();
         my $z0 = sqrt(-2.0 * log($u1)) * cos(2.0 * 3.141592653589793 * $u2);
         my $z1 = sqrt(-2.0 * log($u1)) * sin(2.0 * 3.141592653589793 * $u2); # Scale and shift to match mean and std_dev
         push @numbers, ($z0 * $std_dev + $mean, $z1 * $std_dev + $mean);
     } # Trim to exact size if needed
     @numbers = @numbers[0 .. $size - 1] if @numbers > $size;
     @numbers = map {sprintf '%.1f', $_} @numbers;
     return \@numbers;
 }
 sub rand_between {
     my ($min, $max) = @_;
     return $min + rand($max - $min)
 }

=head2 Barplot/bar/barh

Plot a hash or a hash of arrays as a boxplot

=head3 Options

=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>color</td><td>:mpltype:<code>color</code> or list of :mpltype:<code>color</code>, optional; The colors of the bar faces. This is an alias for *facecolor*. If both are given, *facecolor* takes precedence # if entering multiple colors, quoting isn't needed</td><td><code>color => ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'fuchsia'],</code> or a single color for all bars <code>color => 'red'</code></td></tr>
<tr><td>edgecolor</td><td>:mpltype:<code>color</code> or list of :mpltype:<code>color</code>, optional; The colors of the bar edges</td><td><code>edgecolor     => 'black'</code></td></tr>
<tr><td>key.order</td><td>define the keys in an order (an array reference)</td><td><code>'key.order'        => ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'],</code></td></tr>
<tr><td>linewidth</td><td>float or array, optional; Width of the bar edge(s). If 0, don't draw edges. Only does anything with defined <code>edgecolor</code></td><td><code>linewidth => 2,</code></td></tr>
<tr><td>log</td><td>bool, default: False; If *True*, set the y-axis to be log scale.</td><td><code>log = 'True',</code></td></tr>
<tr><td>stacked</td><td>stack the groups on top of one another; default 0 = off</td><td><code>stacked   => 1,</code></td></tr>
<tr><td>width</td><td>float only, default: 0.8; The width(s) of the bars.  <code>width</code> will be deactivated with grouped, non-stacked bar plots</td><td><code>width => 0.4,</code></td></tr>
<tr><td>xerr</td><td>float or array-like of shape(N,) or shape(2, N), optional. If not *None*, add horizontal / vertical errorbars to the bar tips. The values are +/- sizes relative to the data:        - scalar: symmetric +/- values for all bars #        - shape(N,): symmetric +/- values for each bar #        - shape(2, N): Separate - and + values for each bar. First row #          contains the lower errors, the second row contains the upper #          errors. #        - *None*: No errorbar. (Default)</td><td><code>yerr                       => {'USA'               => [15,29], 'Russia'            => [199,1000],}</code></td></tr>
<tr><td>yerr</td><td>same as xerr, but better with bar</td><td></td></tr>
</tbody>
</table>

an example of multiple plots, showing many options:

=head3 single, simple plot

 use Matplotlib::Simple 'plt';
 plt({
     'output.file'           => 'output.images/single.barplot.png',
     data    => { # simple hash
         Fri => 76, Mon  => 73, Sat => 26, Sun => 11, Thu    => 94, Tue  => 93, Wed  => 77
     },
     'plot.type' => 'bar',
     xlabel      => '# of Days',
     ylabel      => 'Count',
     title       => 'Customer Calls by Days'
 });

where C<xlabel>, C<ylabel>, C<title>, etc. are axis methods in matplotlib itself. C<plot.type>, C<data>, C<fh> are all specific to C<MatPlotLib::Simple>.

=for html
<p>
<img width="651" height="491" alt="single barplot" src="https://github.com/user-attachments/assets/eae009a8-5571-4608-abdb-1016e3cff5fd" />
<p>


=head3 multiple plots

 plt({
     fh                  => $fh,
     execute                => 0,
     'output.file'   => 'output.images/barplots.png',
     plots                   => [
     { # simple plot
             data    => { # simple hash
                 Fri => 76, Mon  => 73, Sat => 26, Sun => 11, Thu    => 94, Tue  => 93, Wed  => 77
             },
             'plot.type' => 'bar',
            'key.order'      => ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'],
             suptitle            => 'Types of Plots', # applies to all
             color               => ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'fuchsia'],
             edgecolor       => 'black',
             set_figwidth    => 40/1.5, # applies to all plots
             set_figheight   => 30/2, # applies to all plots
             title               => 'bar: Rejections During Job Search',
             xlabel          => 'Day of the Week',
             ylabel          => 'No. of Rejections'
         },
         { # grouped bar plot
             'plot.type' => 'bar',
             data    => {
                 1941 => {
                    UK       => 6.6,
                    US       => 6.2,
                    USSR     => 17.8,
                    Germany => 26.6
                 },
                 1942 => {
                   UK      => 7.6,
                   US      => 26.4,
                   USSR    => 19.2,
                   Germany => 29.7
                 },
                 1943 => {
                  UK      => 7.9,
                   US      => 61.4,
                   USSR    => 22.5,
                   Germany => 34.9
                 },
                 1944 => {
                   UK      => 7.4,
                   US      => 80.5,
                   USSR    => 27.0,
                   Germany => 31.4
                 },
                 1945 => {
                   UK      => 5.4,
                   US      => 83.1,
                   USSR    => 25.5,
                   Germany => 11.2 #Rapid decrease due to war's end <br />
                 },
             },
             stacked => 0,
             title       => 'Hash of Hash Grouped Unstacked Barplot',
             width       => 0.23,
             xlabel  => 'r"$\it{anno}$ $\it{domini}$"', # italic
             ylabel  => 'Military Expenditure (Billions of $)'
         },
          { # grouped bar plot
             'plot.type' => 'bar',
             data    => {
                 1941 => {
                   UK      => 6.6,
                   US      => 6.2,
                   USSR    => 17.8,
                   Germany => 26.6
                 },
                 1942 => {
                   UK      => 7.6,
                   US      => 26.4,
                   USSR    => 19.2,
                   Germany => 29.7
                 },
                 1943 => {
                   UK      => 7.9,
                   US      => 61.4,
                   USSR    => 22.5,
                   Germany => 34.9
                 },
                 1944 => {
                   UK      => 7.4,
                   US      => 80.5,
                   USSR    => 27.0,
                   Germany => 31.4
                 },
                 1945 => {
                   UK      => 5.4,
                   US      => 83.1,
                   USSR    => 25.5,
                    Germany => 11.2 #Rapid decrease due to war's end 
                 },
             },
             stacked => 1,
             title       => 'Hash of Hash Grouped Stacked Barplot',
             xlabel  => 'r"$\it{anno}$ $\it{domini}$"', # italic
             ylabel  => 'Military Expenditure (Billions of $)'
         },
         {# grouped barplot: arrays indicate Union, Confederate which must be specified in options hash
             data                    => { # 4th plot: arrays indicate Union, Confederate which must be specified in options hash
              'Antietam'             => [ 12400, 10300 ],
              'Gettysburg'           => [ 23000, 28000 ],
              'Chickamauga'          => [ 16000, 18000 ],
              'Chancellorsville' => [ 17000, 13000 ],
              'Wilderness'           => [ 17500, 11000 ],
              'Spotsylvania'     => [ 18000, 12000 ],
              'Cold Harbor'          => [ 12000, 5000  ],
              'Shiloh'               => [ 13000, 10700 ],
              'Second Bull Run'  => [ 10000, 8000  ],
              'Fredericksburg'       => [ 12600, 5300  ],
             },
             'plot.type' => 'barh',
             color       =>  ['blue', 'gray'], # colors match indices of data arrays
             label       => ['North', 'South'], # colors match indices of data arrays
             xlabel  => 'Casualties',
             ylabel  => 'Battle',
             title       => 'barh: hash of array'
         },
         { # 5th plot: barplot with groups
             data    => {
                 1942 => [ 109867,  310000, 7700000 ], # US, Japan, USSR
                 1943 => [ 221111,  440000, 9000000 ],
                 1944 => [ 318584,  610000, 7000000 ],
                 1945 => [ 318929, 1060000, 3000000 ],
             },
             color       => ['blue', 'pink', 'red'], # colors match indices of data arrays
             label       => ['USA', 'Japan', 'USSR'], # colors match indices of data arrays
             'log'       => 1,
             title       => 'grouped bar: Casualties in WWII',
             ylabel  => 'Casualties',
             'plot.type' => 'bar'
         }, <br />
         { # nuclear weapons barplot
             'plot.type'     => 'bar',
             data => {
                 'USA'               => 5277, # FAS Estimate
                 'Russia'            => 5449, # FAS Estimate
                 'UK'                => 225, # Consistent estimate
                 'France'            => 290, # Consistent estimate
                 'China'         => 600, # FAS Estimate
                 'India'         => 180, # FAS Estimate
                 'Pakistan'      => 130, # FAS Estimate
                 'Israel'            => 90, # FAS Estimate
                 'North Korea'   => 50, # FAS Estimate
             },
             title       => 'Simple hash for barchart with yerr',
             xlabel  => 'Country',
             yerr                        => {
                 'USA'               => [15,29],
                 'Russia'            => [199,1000],
                 'UK'                => [15,19],
                 'France'            => [19,29],
                 'China'         => [200,159],
                 'India'         => [15,25],
                 'Pakistan'      => [15,49],
                 'Israel'            => [90,50],
                 'North Korea'   => [10,20],
             },
             ylabel  => '# of Nuclear Warheads',
             'log'                       => 'True', #    linewidth               => 1,
         }
     ],
     ncols   => 3,
     nrows   => 4
 });

which produces the plot:


=for html
<p>
<img width="2678" height="849" alt="barplots" src="https://github.com/user-attachments/assets/6d87d13b-dabd-485d-92f7-1418f4acc65b" />
<p>


=head2 boxplot

Plot a hash of arrays as a series of boxplots

=head3 options

=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>color</code></td><td>a single color for all plots</td><td><code>color => 'pink'</code></td></tr>
<tr><td><code>colors</code></td><td>a hash, where each data point and color is a hash pair</td><td><code>colors => { A => 'orange', E => 'yellow', B => 'purple' },</code></td></tr>
<tr><td><code>key.order</code></td><td>order that the keys in the entry hash will be plotted</td><td><code>key.order = ['A', 'E', 'B']</code></td></tr>
<tr><td><code>orientation</code></td><td>orientation of the plot, by default <code>vertical</code></td><td><code>orientation = 'horizontal'</code></td></tr>
<tr><td><code>showcaps</code></td><td>Show the caps on the ends of whiskers; default <code>True</code></td><td><code>showcaps => 'False',</code></td></tr>
<tr><td><code>showfliers</code></td><td>Show the outliers beyond the caps; default <code>True</code></td><td><code>showfliers  => 'False'</code></td></tr>
<tr><td><code>showmeans</code></td><td>show means; default = <code>True</code></td><td><code>showmeans   => 'False'</code></td></tr>
<tr><td><code>whiskers</code></td><td>show whiskers, default = 1</td><td><code> whiskers    => 0,</code></td></tr>
</tbody>
</table>

=head3 single, simple plot

 my $x = generate_normal_dist( 100, 15, 3 * 10 );
 my $y = generate_normal_dist( 85,  15, 3 * 10 );
 my $z = generate_normal_dist( 106, 15, 3 * 10 );

single plots are simple

 use Matplotlib::Simple 'barplot';
 barplot({
     'output.file' => 'output.images/single.boxplot.png',
     data              => {                                     # simple hash
         E => [ 55,    @{$x}, 160 ],
         B => [ @{$y}, 140 ],
 
         #       A => @a
     },
     title        => 'Single Box Plot: Specified Colors',
     colors       => { E => 'yellow', B => 'purple' },
     fh           => $fh,
     execute      => 0,
 });

which makes the following image:


=for html
<p>
<img width="651" height="491" alt="single boxplot" src="https://github.com/user-attachments/assets/19870fa2-fe36-4513-8cbb-23da3a0cf686" />
<p>


=head3 multiple plots

 plt({
     'output.file' => 'output.images/boxplot.png',
     execute           => 0,
     fh => $fh,
     plots             => [
         {
             data => {
                 A => [ 55, @{$z} ],
                 E => [ @{$y} ],
                 B => [ 122, @{$z} ],
             },
             title       => 'Simple Boxplot',
             ylabel      => 'ylabel',
             xlabel      => 'label',
             'plot.type' => 'boxplot',
             suptitle    => 'Boxplot examples'
         },
         {
             color => 'pink',
             data  => {
                 A => [ 55, @{$z} ],
                 E => [ @{$y} ],
                 B => [ 122, @{$z} ],
             },
             title       => 'Specify single color',
             ylabel      => 'ylabel',
             xlabel      => 'label',
             'plot.type' => 'boxplot'
         },
         {
             colors => {
                 A => 'orange',
                 E => 'yellow',
                 B => 'purple'
             },
             data => {
                 A => [ 55, @{$z} ],
                 E => [ @{$y} ],
                 B => [ 122, @{$z} ],
             },
             title       => 'Specify set-specific color; showfliers = False',
             ylabel      => 'ylabel',
             xlabel      => 'label',
             'plot.type' => 'boxplot',
             showmeans   => 'True',
             showfliers  => 'False',
             set_figwidth => 12
         },
         {
             colors => {
                 A => 'orange',
                 E => 'yellow',
                 B => 'purple'
             },
             data => {
                 A => [ 55, @{$z} ],
                 E => [ @{$y} ],
                 B => [ 122, @{$z} ],
             },
             title       => 'Specify set-specific color; showmeans = False',
             ylabel      => 'ylabel',
             xlabel      => 'label',
             'plot.type' => 'boxplot',
             showmeans   => 'False',
         },
         {
             colors => {
                 A => 'orange',
                 E => 'yellow',
                 B => 'purple'
             },
             data => {
                 A => [ 55, @{$z} ],
                 E => [ @{$y} ],
                 B => [ 122, @{$z} ],
             },
             title       => 'Set-specific color; orientation = horizontal',
             ylabel      => 'ylabel',
             xlabel      => 'label',
             orientation => 'horizontal',
             'plot.type' => 'boxplot',
         },
         {
             colors => {
                 A => 'orange',
                 E => 'yellow',
                 B => 'purple'
             },
             data => {
                 A => [ 55, @{$z} ],
                 E => [ @{$y} ],
                 B => [ 122, @{$z} ],
             },
             title       => 'Notch = True',
             ylabel      => 'ylabel',
             xlabel      => 'label',
             notch       => 'True',
             'plot.type' => 'boxplot',
         },
         {
             colors => {
                 A => 'orange',
                 E => 'yellow',
                 B => 'purple'
             },
             data => {
                 A => [ 55, @{$z} ],
                 E => [ @{$y} ],
                 B => [ 122, @{$z} ],
             },
             title         => 'showcaps = False',
             ylabel        => 'ylabel',
             xlabel        => 'label',
             showcaps      => 'False',
             'plot.type'   => 'boxplot',
             set_figheight => 12,
         },
     ],
     ncols => 3,
     nrows => 3,
 });

which makes the following plot:


=for html
<p>
<img width="1230" height="1211" alt="boxplot" src="https://github.com/user-attachments/assets/7e32e394-86fc-49e7-ad97-f48fd82fc8b0" />
<p>


=head2 Colored Table

=head3 options

=head3 Single, simple plot

the bond dissociation energy table can be plotted:

 # https://labs.chem.ucsb.edu/zakarian/armen/11---bonddissociationenergy.pdf and https://chem.libretexts.org/Bookshelves/Physical_and_Theoretical_Chemistry_Textbook_Maps/Supplemental_Modules_(Physical_and_Theoretical_Chemistry)/Chemical_Bonding/Fundamentals_of_Chemical_Bonding/Bond_Energies
 my %bond_dissociation = (
     Br =>  {
       Br =>  193
     },
     C  =>  {
         Br =>  276, C  =>  347, Cl =>  339, F   => 485, H  =>  413, I  =>  240,
         N  =>  305, O  =>  358, S  =>  259
     },
     Cl =>  {
         Br =>  218, Cl =>  239
     },
     F =>   {
         I => 280, Br =>  237, Cl  => 253, F   => 154
     },
     H  =>  {
         Br =>  363, Cl =>  427, F  =>  565, H   => 432, I   => 295
     },
     I  =>  {
         Br  => 175, Cl =>  208, I  =>  149
     },
     N  =>  {
         Br =>  243, Cl  => 200, F   => 272, H  =>  391, N  =>  160, O  =>  201
     },
     O =>   {
         Cl =>  203, F  =>  190, H  =>  467, I  =>  234, O  =>  146
     },
     S  =>  {
         Br => 218,  Cl => 253,  F  => 327,  H  => 347,  S  => 266
     },
     Si => {
         C  => 360, H  => 393, O  => 452,    Si => 340
     }
 );

and the plot itself:

 colored_table({
     'cblabel'     => 'kJ/mol',
     'col.labels'  => ['H', 'F', 'Cl', 'Br', 'I'],
     data          => \%bond_dissociation,
     execute       => 0,
     fh            => $fh,
     mirror        => 1,
     'output.file' => 'output.images/single.tab.png',
     'row.labels'  => ['H', 'F', 'Cl', 'Br', 'I'],
     'show.numbers'=> 1,
     set_title     => 'Bond Dissociation Energy'
 });

which makes the following image:


=for html
<p>
<img width="584" height="491" alt="single tab" src="https://github.com/user-attachments/assets/d890830b-a502-4d51-b118-20aeae0473e8" />
<p>


=head3 Multiple Plots

 plt({
     'output.file' => 'output.images/tab.multiple.png',
     execute       => 0,
     fh            => $fh,
     plots         => [
         {
             data          => \%bond_dissociation,
             'output.file' => '/tmp/single.bonds.svg',
             'plot.type'   => 'colored_table',
             set_title     => 'No other options'
         },
         {
             data          => \%bond_dissociation,
             cblabel       => 'Average Dissociation Energy (kJ/mol)',
             'col.labels'  => ['H', 'C', 'N', 'O', 'F', 'Si', 'S', 'Cl', 'Br', 'I'],
             mirror        => 1,
             'output.file' => '/tmp/single.bonds.svg',
             'plot.type'   => 'colored_table',
             'row.labels'  => ['H', 'C', 'N', 'O', 'F', 'Si', 'S', 'Cl', 'Br', 'I'],
             'show.numbers'=> 1,
             set_title     => 'Showing numbers and mirror with defined order'
         },
         {
             data          => \%bond_dissociation,
             cblabel       => 'Average Dissociation Energy (kJ/mol)',
             'col.labels'  => ['H', 'C', 'N', 'O', 'F', 'Si', 'S', 'Cl', 'Br', 'I'],
             mirror        => 1,
             'output.file' => '/tmp/single.bonds.svg',
             'plot.type'   => 'colored_table',
             'row.labels'  => ['H', 'C', 'N', 'O', 'F', 'Si', 'S', 'Cl', 'Br', 'I'],
             'show.numbers'=> 1,
             set_title     => 'Set undefined color to white',
             'undef.color' => 'white'
         }
     ],
     ncols         => 3,
     set_figwidth  => 14,
     suptitle      => 'Colored Table options'
 });

which makes the following plot:


=for html
<p>
<img width="1410" height="491" alt="tab multiple" src="https://github.com/user-attachments/assets/be836742-cc5b-4618-a0c8-a0ee57856eb1" />
<p>


=head2 hexbin

Plot a hash of arrays as a hexbin
see https://matplotlib.org/stable/api/I<as>gen/matplotlib.pyplot.hexbin.html

=head3 options

=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>cb_logscale</td><td>colorbar log scale <code>from matplotlib.colors import LogNorm</code></td><td>default 0, any value > 0 enables</td></tr>
<tr><td>cmap</td><td>The Colormap instance or registered colormap name used to map scalar data to colors</td><td>default <code>gist_rainbow</code></td></tr>
<tr><td>key.order</td><td>define the keys in an order (an array reference)</td><td><code>'key.order' => ['X-rays', 'Yak Butter'],</code></td></tr>
<tr><td>marginals</td><td>integer, by default off = 0</td><td><code>marginals => 1</code></td></tr>
<tr><td>mincnt</td><td>int >= 0, default: None; If not None, only display cells with at least mincnt number of points in the cell.</td><td><code>mincnt => 2</code></td></tr>
<tr><td>vmax</td><td>The normalization method used to scale scalar data to the [0, 1] range before mapping to colors using cmap</td><td><code>'asinh', 'function', 'functionlog', 'linear', 'log', 'logit', 'symlog'</code> default <code>linear</code></td></tr>
<tr><td>vmin</td><td>The normalization method used to scale scalar data to the [0, 1] range before mapping to colors using cmap</td><td><code>'asinh', 'function', 'functionlog', 'linear', 'log', 'logit', 'symlog'</code> default <code>linear</code></td></tr>
<tr><td>xbins</td><td>integer that accesses horizontal gridsize</td><td>default is 15</td></tr>
<tr><td>xscale.hexbin</td><td>'linear', 'log'}, default: 'linear': Use a linear or log10 scale on the horizontal axis</td><td><code>'xscale.hexbin' => 'log'</code></td></tr>
<tr><td>ybins</td><td>integer that accesses vertical gridsize</td><td>default is 15</td></tr>
<tr><td>yscale.hexbin</td><td>'linear', 'log'}, default: 'linear': Use a linear or log10 scale on the vertical axis</td><td><code>'yscale.hexbin' => 'log'</code></td></tr>
</tbody>
</table>

=head3 single, simple plot

 plt({
     data    => {
         E   => generate_normal_dist(100, 15, 3*210),
         B   => generate_normal_dist(85, 15, 3*210)
     },
     'output.file'   => 'output.images/single.hexbin.png',
     'plot.type' => 'hexbin',
     set_figwidth => 12,
     title           => 'Simple Hexbin',
 });

which makes the following plot:

=for html
<p>
<img width="1208" height="491" alt="single hexbin" src="https://github.com/user-attachments/assets/129c41cd-2d7d-43de-978a-2b9c441b8939" />
<p>

=head3 multiple plots

 plt({
     fh => $fh,
     execute           => 0,
     'output.file' => 'output.images/hexbin.png',
     plots             => [
         {
             data => {
             E => @e,
             B => @b
             },
             'plot.type'  => 'hexbin',
             title        => 'Simple Hexbin',
         },
         {
             data => {
                 E => @e,
                 B => @b
             },
             'plot.type' => 'hexbin',
             title       => 'colorbar logscale',
             cb_logscale => 1
         },
         {
             cmap => 'jet',
             data => {
                 E => @e,
                 B => @b
             },
             'plot.type'  => 'hexbin',
             title        => 'cmap is jet',
             xlabel       => 'xlabel',
         },
          {
             data => {
                 E => @e,
                 B => @b
             },
             'key.order'  => ['E', 'B'],
             'plot.type'  => 'hexbin',
             title        => 'Switch axes with key.order',
         },
          {
             data => {
                 E => @e,
                 B => @b
             },
             'plot.type'  => 'hexbin',
             title        => 'vmax set to 25',
             vmax         => 25
         },
          {
             data => {
                 E => @e,
                 B => @b
             },
             'plot.type'  => 'hexbin',
             title        => 'vmin set to -4',
             vmin         => -4
         },
         {
             data => {
                 E => @e,
                 B => @b
             },
             'plot.type'  => 'hexbin',
             title        => 'mincnt set to 7',
             mincnt       => 7
         },
         {
             data => {
                 E => @e,
                 B => @b
             },
             'plot.type'  => 'hexbin',
             title        => 'xbins set to 9',
             xbins        => 9
         },
         {
             data => {
                 E => @e,
                 B => @b
             },
             'plot.type'  => 'hexbin',
             title        => 'ybins set to 9',
             ybins        => 9
         },
         {
             data => {
                 E => @e,
                 B => @b
             },
             'plot.type'  => 'hexbin',
             title        => 'marginals = 1',
             marginals    => 1
         },
         {
             data => {
                 E => @e,
                 B => @b
             },
             'plot.type'  => 'hexbin',
             title        => 'xscale.hexbin = 1',
             'xscale.hexbin' => 'log'
         },
         {
             data => {
                 E => @e,
                 B => @b
             },
             'plot.type'  => 'hexbin',
             title        => 'yscale.hexbin = 1',
             'yscale.hexbin' => 'log'
         },
     ],
     ncols        => 4,
     nrows        => 3,
     scale        => 5,
     suptitle     => 'Various Changes to Standard Hexbin: All data is the same'
 });

which produces the following image:

=for html
<p>
<img width="2409" height="3211" alt="hexbin" src="https://github.com/user-attachments/assets/0b23a0cb-8f9a-43fb-8da1-0debee13d540" />
<p>


=head2 hist

Plot a hash of arrays as a series of histograms

=head3 options

=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>alpha</code></td><td>default 0.5; same for all sets</td><td></td></tr>
<tr><td><code>bins</code></td><td># nt or sequence or str, default: :rc:<code>hist.bins</code>If *bins* is an integer, it defines the number of equal-width bins in the range. If *bins* is a sequence, it defines the bin edges, including the left edge of the first bin and the right edge of the last bin; in this case, bins may be unequally spaced.  All but the last  (righthand-most) bin is half-open</td><td></td></tr>
<tr><td><code>color</code></td><td>a hash, where keys are the keys in data, and values are colors</td><td><code>X => 'blue'</code></td></tr>
<tr><td><code>log</code></td><td>if set to > 1, the y-axis will be logarithmic</td><td></td></tr>
<tr><td><code>orientation</code></td><td>{'vertical', 'horizontal'}, default: 'vertical'</td><td></td></tr>
</tbody>
</table>

=head3 single, simple plot

 use Matplotlib::Simple 'hist';
 
 my @e = generate_normal_dist( 100, 15, 3 * 200 );
 my @b = generate_normal_dist( 85,  15, 3 * 200 );
 my @a = generate_normal_dist( 105, 15, 3 * 200 );
 
 hist({
     fh => $fh,
     execute           => 0,
     'output.file' => 'output.images/single.hist.png',
     data              => {
         E => @e,
         B => @b,
         A => @a,
     }
 });

which makes the following simple plot:


=for html
<p>
<img width="651" height="491" alt="single hist" src="https://github.com/user-attachments/assets/fafcf787-6c4f-4998-88c4-77a15d878fa6" />
<p>


=head3 multiple plots

 plt({
     fh => $fh,
     execute           => 0,
     'output.file' => 'output.images/histogram.png',
    set_figwidth => 15,
    suptitle          => 'hist Examples',
     plots             => [
         { # 1st subplot
             data => {
                 E => @e,
                 B => @b,
                 A => @a,
             },
             'plot.type' => 'hist',
             alpha       => 0.25,
             bins        => 50,
             title       => 'alpha = 0.25',
             color       => {
                 B => 'Black',
                 E => 'Orange',
                 A => 'Yellow',
             },
             scatter => '['
               . join( ',', 22 .. 44 ) . '],['  # x coords
               . join( ',', 22 .. 44 )          # y coords
               . '], label = "scatter"',
             xlabel   => 'Value',
             ylabel   => 'Frequency',
         },
         { # 2nd subplot
             data => {
                 E => @e,
                 B => @b,
                 A => @a,
             },
             'plot.type' => 'hist',
             alpha       => 0.75,
             bins        => 50,
             title       => 'alpha = 0.75',
             color       => {
                 B => 'Black',
                 E => 'Orange',
                 A => 'Yellow',
             },
             xlabel   => 'Value',
             ylabel   => 'Frequency',
         },
         { # 3rd subplot
             add               => [ # add secondary plots/graphs/methods
             { # 1st additional plot/graph
                 data              => {
                     'Gaussian'       => [
                         [40..150],
                         [map {150 * exp(-0.5*($_-100)**2)} 40..150]
                     ]
                 },
                 'plot.type' => 'plot',
                 'set.options' => {
                     'Gaussian' =>  'color = "red", linestyle = "dashed"'
                 }
             }
             ],
            data => {
                 E => @e,
                 B => @b,
                 A => @a,
             },
             'plot.type' => 'hist',
             alpha       => 0.75,
             bins        => {
                 A => 10,
                 B => 25,
                 E => 50
             },
             title => 'Varying # of bins',
             color => {
                 B => 'Black',
                 E => 'Orange',
                 A => 'Yellow',
             },
             xlabel       => 'Value',
             ylabel       => 'Frequency',
         },
         {# 4th subplot
             data => {
                 E => @e,
                 B => @b,
                 A => @a,
             },
             'plot.type' => 'hist',
             alpha       => 0.75,
             color       => {
                 B => 'Black',
                 E => 'Orange',
                 A => 'Yellow',
             },
             orientation  => 'horizontal',    # assign x and y labels smartly
             title        => 'Horizontal orientation',
             ylabel       => 'Value',
             xlabel       => 'Frequency',                #               'log'                   => 1,
         },
     ],
     ncols => 3,
     nrows => 2,
 });


=for html
<p>
<img width="1511" height="491" alt="histogram" src="https://github.com/user-attachments/assets/b13b4cc8-6e64-40b0-913d-6a5886cee0db" />
<p>


Make a 2-D histogram from a hash of arrays

=head2 hist2d

=head3 single, simple plot

 plt({
     'output.file' => 'output.images/single.hist2d.png',
     data              => {
         E => @e,
         B => @b
     },
     'plot.type'  => 'hist2d',
     title        => 'title',
     execute      => 0,
     fh => $fh,
 });

makes the following image:


=for html
<p>
<img width="650" height="491" alt="single hist2d" src="https://github.com/user-attachments/assets/86480c77-7b8f-4bfa-b5d8-71f82830260f" />
<p>


the range for the density min and max is reported to stdout

=head3 options

=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>cb_logscale</code></td><td>make the colorbar log-scale</td><td><code>cb_logscale => 1</code></td></tr>
<tr><td><code>cmap</code></td><td>color map for coloring # "gist_rainbow" by default</td><td></td></tr>
<tr><td>'cmax', <code>cmin</code></td><td>All bins that has count < *cmin* or > *cmax* will not be displayed</td><td></td></tr>
<tr><td>'density'</td><td>density : bool, default: False</td><td></td></tr>
<tr><td>'key.order'</td><td>define the keys in an order (an array reference)</td><td></td></tr>
<tr><td>'logscale'</td><td># logscale, an array of axes that will get log scale</td><td></td></tr>
<tr><td>'show.colorbar'</td><td>self-evident, 0 or 1</td><td><code>show.colorbar</code> => 1</td></tr>
<tr><td>'vmax'</td><td>When using scalar data and no explicit *norm*, *vmin* and *vmax* define the data range that the colormap cover</td><td></td></tr>
<tr><td>'vmin'</td><td># When using scalar data and no explicit *norm*, *vmin* and *vmax* define the data range that the colormap cover</td><td></td></tr>
<tr><td>'xbins'</td><td># default 15</td><td></td></tr>
<tr><td>'xmin', 'xmax',</td><td></td><td></td></tr>
<tr><td>'ymin', 'ymax',</td><td></td><td></td></tr>
<tr><td>'ybins'</td><td>default 15</td><td></td></tr>
</tbody>
</table>

=head3 multiple plots

 plt({
     fh => $fh,
     execute           => 1,
     ncols             => 3,
     nrows             => 3,
     suptitle          => 'Types of hist2d plots: all of the data is identical',
     plots => [
         {
             data => {
             X => $x,    # x-axis
             Y => $y,    # y-axis
             },
             'plot.type' => 'hist2d',
             title       => 'Simple hist2d',
         },
         {
             data => {
                 X => $x,    # x-axis
                 Y => $y,    # y-axis
             },
             'plot.type' => 'hist2d',
             title       => 'cmap = terrain',
             cmap        => 'terrain'
         },
         {
             cmap => 'ocean',
             data => {
                 X => $x,    # x-axis
                 Y => $y,    # y-axis
             },
             'plot.type' => 'hist2d',
             title => 'cmap = ocean and set colorbar range with vmin/vmax',
             set_figwidth => 15,
             vmin         => -2,
             vmax         => 14
         },
         {
             data => {
                 X => $x,    # x-axis
                 Y => $y,    # y-axis
             },
             'plot.type' => 'hist2d',
             title       => 'density = True',
             cmap        => 'terrain',
             density     => 'True'
         },
         {
             data => {
                 X => $x,    # x-axis
                 Y => $y,    # y-axis
             },
             'plot.type' => 'hist2d',
             title       => 'key.order flips axes',
             cmap        => 'terrain',
             'key.order' => [ 'Y', 'X' ]
         },
         {
             cb_logscale => 1,
             data => {
                 X => $x,    # x-axis
                 Y => $y,    # y-axis
             },
             'plot.type' => 'hist2d',
             title       => 'cb_logscale = 1',
         },
         {
             cb_logscale => 1,
             data => {
                 X => $x,    # x-axis
                 Y => $y,    # y-axis
             },
             'plot.type' => 'hist2d',
             title       => 'cb_logscale = 1 with vmax set',
             vmax        => 2.1,
             vmin        => 1
         },
         {
             data => {
                 X => $x,    # x-axis
                 Y => $y,    # y-axis
             },
             'plot.type'     => 'hist2d',
             'show.colorbar' => 0,
             title           => 'no colorbar',
         },
         {
             data => {
                 X => $x,    # x-axis
                 Y => $y,    # y-axis
             },
             'plot.type'     => 'hist2d',
             title           => 'xbins = 9',
             xbins           => 9
         },
     ],
     'output.file' => 'output.images/hist2d.png',
 });

makes the following image:


=for html
<p>
<img width="1510" height="491" alt="hist2d" src="https://github.com/user-attachments/assets/3d6becd3-44f3-4511-8b0f-eae39bc325fa" />
<p>


=head2 imshow

Plot 2D array of numbers as an image

=head3 options

=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>cblabel</code></td><td>colorbar label</td><td><code>cblabel => 'sin(x) * cos(x)',</code></td></tr>
<tr><td><code>cbdrawedges</code></td><td>draw edges for colorbar</td><td></td></tr>
<tr><td><code>cblocation</code></td><td>'left', 'right', 'top', 'bottom'</td><td><code>cblocation => 'left',</code></td></tr>
<tr><td><code>cborientation</code></td><td>None, or 'vertical', 'horizontal'</td><td></td></tr>
<tr><td><code>cmap</code></td><td># The Colormap instance or registered colormap name used to map scalar data to colors.</td><td></td></tr>
<tr><td><code>vmax</code></td><td>float</td><td></td></tr>
<tr><td><code>vmin</code></td><td>float</td><td></td></tr>
</tbody>
</table>

=head3 single, simple plot

 my @imshow_data;
 foreach my $i (0..360) {
     foreach my $j (0..360) {
         push @{ $imshow_data[$i] }, sin($i * $pi/180)*cos($j * $pi/180);
     }
 }
 plt({
     data              => \@imshow_data,
     execute           => 0,
    fh => $fh,
     'output.file' => 'output.images/imshow.single.png',
     'plot.type'       => 'imshow',
     set_xlim          => '0, ' . scalar @imshow_data,
     set_ylim          => '0, ' . scalar @imshow_data,
 });

which makes the following image:


=for html
<p>
<img width="599" height="491" alt="imshow single" src="https://github.com/user-attachments/assets/3fa4ffe6-4817-4133-9c91-b68099400377" />
<p>


=head3 multiple plots

 plt({
     plots  => [
         {
             data => \@imshow_data,
             'plot.type'       => 'imshow',
             set_xlim          => '0, ' . scalar @imshow_data,
             set_ylim          => '0, ' . scalar @imshow_data,
             title             => 'basic',
         },
         {
             cblabel           => 'sin(x) * cos(x)',
             data => \@imshow_data,
             'plot.type'       => 'imshow',
             set_xlim          => '0, ' . scalar @imshow_data,
             set_ylim          => '0, ' . scalar @imshow_data,
             title             => 'cblabel',
         },
         {
             cblabel           => 'sin(x) * cos(x)',
             cblocation        => 'left',
             data              => \@imshow_data,
             'plot.type'       => 'imshow',
             set_xlim          => '0, ' . scalar @imshow_data,
             set_ylim          => '0, ' . scalar @imshow_data,
             title             => 'cblocation = left',
         },
         {
             cblabel           => 'sin(x) * cos(x)',
             data              => \@imshow_data,
             add               => [ # add secondary plots
             { # 1st additional plot
                 data              => {
                     'sin(x)'       => [
                         [0..360],
                         [map {180 + 180*sin($_ * $pi/180)} 0..360]
                     ],
                     'cos(x)'       => [
                         [0..360],
                         [map {180 + 180*cos($_ * $pi/180)} 0..360]
                     ],
                 },
                 'plot.type' => 'plot',
                 'set.options' => {
                     'sin(x)'    =>  'color = "red", linestyle = "dashed"',
                     'cos(x)'    =>  'color = "blue", linestyle = "dashed"',
                 }
             }
             ],
             'plot.type'       => 'imshow',
             set_xlim          => '0, ' . scalar @imshow_data,
             set_ylim          => '0, ' . scalar @imshow_data,
             title             => 'auxiliary plots',
         },
     ],
     execute         => 0,
   fh              => $fh,
     'output.file'   => 'output.images/imshow.multiple.png',
     ncols           => 2,
     nrows           => 2,
     set_figheight   => 6*3,# 4.8
     set_figwidth    => 6*4 # 6.4
 });

which makes the following image:


=for html
<p>
<img width="2416" height="1811" alt="imshow multiple" src="https://github.com/user-attachments/assets/091acccb-151c-47ca-82cc-99c19d2bff91" />
<p>


=head2 pie

=head3 options

=head3 single, simple plot

 plt({
     'output.file' => 'output.images/single.pie.png',
     data              => {                                 # simple hash
         Fri => 76,
         Mon => 73,
         Sat => 26,
         Sun => 11,
         Thu => 94,
         Tue => 93,
         Wed => 77
     },
     'plot.type'  => 'pie',
     title        => 'Single Simple Pie',
     fh           => $fh,
     execute      => 0,
 });

which makes the image:


=for html
<p>
<img width="469" height="491" alt="single pie" src="https://github.com/user-attachments/assets/a0bc3212-d013-463a-9be6-f96829ac7dba" />
<p>


=head3 multiple plots

 plt({
     'output.file' => 'output.images/pie.png',
     plots             => [
         {
             data => {
                 'Russian' => 106_000_000,    # Primarily European Russia
                 'German'  =>
                   95_000_000,    # Germany, Austria, Switzerland, etc.
                 'English' => 70_000_000,      # UK, Ireland, etc.
                 'French' => 66_000_000, # France, Belgium, Switzerland, etc.
                 'Italian'   => 59_000_000,    # Italy, Switzerland, etc.
                 'Spanish'   => 45_000_000,    # Spain
                 'Polish'    => 38_000_000,    # Poland
                 'Ukrainian' => 32_000_000,    # Ukraine
                 'Romanian'  => 24_000_000,    # Romania, Moldova
                 'Dutch'     => 22_000_000     # Netherlands, Belgium
             },
             '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.
                 'English' => 70_000_000,      # UK, Ireland, etc.
                 'French' => 66_000_000, # France, Belgium, Switzerland, etc.
                 'Italian'   => 59_000_000,    # Italy, Switzerland, etc.
                 'Spanish'   => 45_000_000,    # Spain
                 'Polish'    => 38_000_000,    # Poland
                 'Ukrainian' => 32_000_000,    # Ukraine
                 'Romanian'  => 24_000_000,    # Romania, Moldova
                 'Dutch'     => 22_000_000     # Netherlands, Belgium
             },
             'plot.type' => 'pie',
             title       => 'Top Languages in Europe',
             autopct     => '%1.1f%%',
         },
         {
             data => {
                 'United States'  => 86,
                 'United Kingdom' => 33,
                 'Germany'        => 29,
                 'France'         => 10,
                 'Japan'          => 7,
                 'Israel'         => 6,
             },
             title         => 'Chem. Nobels: swap text positions',
             'plot.type'   => 'pie',
             autopct       => '%1.1f%%',
             pctdistance   => 1.25,
             labeldistance => 0.6,
         }
     ],
     fh => $fh,
     execute      => 0,
    set_figwidth  => 12,
     ncols        => 3,
 });


=for html
<p>
<img width="1210" height="444" alt="pie" src="https://github.com/user-attachments/assets/4c44d300-fd84-49bc-9a32-b73af54286cf" />
<p>


=head2 plot

plot either a hash of arrays or an array of arrays

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


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


=head3 multiple sub-plots

which makes

 my $epsilon = 10**-7;
 my (%set_opt, %d);
 my $i = 0;
 foreach my $interval (
     [-2*$pi, -$pi],
     [-$pi, 0],
     [0, $pi],
     [$pi, 2*$pi]
 ) {
     my @th = linspace($interval->[0] + $epsilon, $interval->[1] - $epsilon, 99, 0);
     @{ $d{csc}{$i}[0] } = @th;
     @{ $d{csc}{$i}[1] } = map { 1/sin($_) } @th;
     @{ $d{cot}{$i}[0] } = @th;
     @{ $d{cot}{$i}[1] } = map { cos($_)/sin($_) } @th;
     if ($i == 0) {
         $set_opt{csc}{$i} = 'color = "red", label = "csc(θ)"';
         $set_opt{cot}{$i} = 'color = "violet", label = "cot(θ)"';
     } else {
         $set_opt{csc}{$i} = 'color = "red"';
         $set_opt{cot}{$i} = 'color = "violet"';
     }
     $i++;
 }
 $i = 0;
 foreach my $interval (
     [-2 * $pi, -1.5 * $pi],
     [-1.5*$pi, -0.5*$pi],
     [-0.5*$pi, 0.5 * $pi],
     [0.5 * $pi, 1.5 * $pi],
     [1.5 * $pi, 2 * $pi]
 ) {
     my @th = linspace($interval->[0] + $epsilon, $interval->[1] - $epsilon, 99, 0);
     @{ $d{sec}{$i}[0] } = @th;
     @{ $d{sec}{$i}[1] } = map { 1/cos($_) } @th;
     if ($i == 0) {
         $set_opt{sec}{$i} = 'color = "blue", label = "sec(θ)"';
         $set_opt{tan}{$i} = 'color = "green", label = "tan(θ)"';
     } else {
         $set_opt{sec}{$i} = 'color = "blue"';
         $set_opt{tan}{$i} = 'color = "green"';
     }
     @{ $d{tan}{$i}[0] } = @th;
     @{ $d{tan}{$i}[1] } = map { sin($_)/cos($_) } @th;
     $i++;
 }
 mkdir 'svg' unless -d 'svg';
 my $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$\']';
 my ($min, $max) = (-9,9);
 plt({
     fh => $fh,
     execute           => 0,
     'output.file' => 'output.images/plots.png',
     plots         => [
     { # sin
         data          => {
             'sin(θ)' => [
                 [@x],
                 [map {sin($_)} @x]
             ]
         },
         'plot.type'   => 'plot',
         'set.options' => {
             'sin(θ)' => 'color = "orange"'
         },
         set_xticks    => $xticks,
         set_xlim      => "-2*$pi, 2*$pi",
         xlabel        => 'θ',
         ylabel        => 'sin(θ)',
     },
     { # sin
         data          => {
             'cos(θ)' => [
                 [@x],
                 [map {cos($_)} @x]
             ]
         },
         'plot.type'   => 'plot',
         'set.options' => {
             'cos(θ)' => 'color = "black"'
         },
         set_xticks    => $xticks,
         set_xlim      => "-2*$pi, 2*$pi",
         xlabel        => 'θ',
         ylabel        => 'cos(θ)',
     },
     { # csc
         data          => $d{csc},
         'plot.type'   => 'plot',
         'set.options' => $set_opt{csc},
         set_xticks    => $xticks,
         set_xlim      => "-2*$pi, 2*$pi",
         set_ylim      => "$min,$max",
         'show.legend' => 0,
         vlines        => [ # asymptotes
             "-2*$pi, $min, $max, color = 'gray', linestyle = 'dashed'",
             "-$pi, $min, $max, color = 'gray', linestyle = 'dashed'",
             "0, $min, $max, color = 'gray', linestyle = 'dashed'",
             "$pi, $min, $max, color = 'gray', linestyle = 'dashed'",
             "2*$pi, $min, $max, color = 'gray', linestyle = 'dashed'",
         ],
         xlabel        => 'θ',
         ylabel        => 'csc(θ)',
     },
     { # sec
         data          => $d{sec},
         'plot.type'   => 'plot',
         'set.options' => $set_opt{sec},
         set_xticks    => $xticks,
         set_xlim      => "-2*$pi, 2*$pi",
         set_ylim      => "$min,$max",
         'show.legend' => 0,
         vlines        => [ # asymptotes
             "-1.5*$pi, $min, $max, color = 'gray', linestyle = 'dashed'",
             "-.5*$pi, $min, $max, color = 'gray', linestyle = 'dashed'",
             ".5*$pi, $min, $max, color = 'gray', linestyle = 'dashed'",
             "1.5*$pi, $min, $max, color = 'gray', linestyle = 'dashed'",
 #           "2*$pi, $min, $max, color = 'gray', linestyle = 'dashed'",
         ],
         xlabel        => 'θ',
         ylabel        => 'sec(θ)',
     },
         { # csc
         data          => $d{cot},
         'plot.type'   => 'plot',
         'set.options' => $set_opt{cot},
         set_xticks    => $xticks,
         set_xlim      => "-2*$pi, 2*$pi",
         set_ylim      => "$min,$max",
         'show.legend' => 0,
         vlines        => [ # asymptotes
             "-2*$pi, $min, $max, color = 'gray', linestyle = 'dashed'",
             "-$pi, $min, $max, color = 'gray', linestyle = 'dashed'",
             "0, $min, $max, color = 'gray', linestyle = 'dashed'",
             "$pi, $min, $max, color = 'gray', linestyle = 'dashed'",
             "2*$pi, $min, $max, color = 'gray', linestyle = 'dashed'",
         ],
         xlabel        => 'θ',
         ylabel        => 'cot(θ)',
     },
     { # sec
         data          => $d{tan},
         'plot.type'   => 'plot',
         'set.options' => $set_opt{tan},
         set_xticks    => $xticks,
         set_xlim      => "-2*$pi, 2*$pi",
         set_ylim      => "$min,$max",
         'show.legend' => 0,
         vlines        => [ # asymptotes
             "-1.5*$pi, $min, $max, color = 'gray', linestyle = 'dashed'",
             "-.5*$pi, $min, $max, color = 'gray', linestyle = 'dashed'",
             ".5*$pi, $min, $max, color = 'gray', linestyle = 'dashed'",
             "1.5*$pi, $min, $max, color = 'gray', linestyle = 'dashed'",
 #           "2*$pi, $min, $max, color = 'gray', linestyle = 'dashed'",
         ],
         xlabel        => 'θ',
         ylabel        => 'tan(θ)',
     },
     ], # end
     ncols        => 2,
     nrows        => 3,
     set_figwidth => 8,
     suptitle     => 'Basic Trigonometric Functions'
 });


=for html
<p>
<img width="811" height="491" alt="plots" src="https://github.com/user-attachments/assets/0bdd0744-c1bb-4c4a-9482-b3de3f2d4fc2" />
<p>


=head2 scatter

=head3 single, simple plot

 scatter({
     fh            => $fh,
     data          => {
         X => [@x],
         Y => [map {sin($_)} @x]
     },
     execute       => 0,
     'output.file' => 'output.images/single.scatter.png',
 });

makes the following image:


=for html
<p>
<img width="651" height="491" alt="single scatter" src="https://github.com/user-attachments/assets/c45d9922-23e0-4f85-8306-aa7fca400328" />
<p>


=head3 options

=head3 multiple plots

 plt({
     fh => $fh,
     'output.file' => 'output.images/scatterplots.png',
     execute           => 0,
     nrows             => 2,
     ncols             => 3,
     set_figheight     => 8,
     set_figwidth      => 16,
     suptitle          => 'Scatterplot Examples',            # applies to all
     plots             => [
         {    # single-set scatter; no label
             data => {
                 X => @e,    # x-axis
                 Y => @b,    # y-axis
                 Z => @a     # color
             },
             title     => '"Single Set Scatterplot: Random Distributions"',
             color_key => 'Z',
             'set.options' => 'marker = "v"'
             , # arguments to ax.scatter: there's only 1 set, so "set.options" is a scalar
             text        => [ '100, 100, "text1"', '100, 100, "text2"', ],
             'plot.type' => 'scatter',
         },
         {     # multiple-set scatter, labels are "X" and "Y"
             data => {
                 X => {    # 1st data set; label is "X"
                     A => @a,    # x-axis
                     B => @b,    # y-axis
                 },
                 W => {    # 2nd data set; label is "Y"
                     A => generate_normal_dist( 100, 15, 210 ),    # x-axis
                     B => generate_normal_dist( 100, 15, 210 ),    # y-axis
                 }
             },
             'plot.type'   => 'scatter',
             title         => 'Multiple Set Scatterplot',
             'set.options' =>
             {    # arguments to ax.scatter, for each set in data
               X => 'marker = ".", color = "red"',
               W => 'marker = "d", color = "green"'
             },
         },
         {          # multiple-set scatter, labels are "X" and "Y"
             data => {    # 8th plot,
                 X => {    # 1st data set; label is "X"
                     A => @e,    # x-axis
                     B => @b,    # y-axis
                     C => @a,    # color
                 },
                 Y => {    # 2nd data set; label is "Y"
                     A => generate_normal_dist( 100, 15, 210 ),    # x-axis
                     B => generate_normal_dist( 100, 15, 210 ),    # y-axis
                     C => generate_normal_dist( 100, 15, 210 ),    # color
                 },
             },
             'plot.type'   => 'scatter',
             title         => 'Multiple Set Scatter w/ colorbar',
             'set.options' => {    # arguments to ax.scatter, for each set in data
                 X => 'marker = "."',    # diamond
                 Y => 'marker = "d"'     # diamond
             },
             color_key => 'Z',
         }
     ]
 });

which makes the following figure:


=for html
<p>
<img width="1610" height="461" alt="scatterplots" src="https://github.com/user-attachments/assets/b8a90f9f-acb3-4cf2-a423-6ad18686ab8c" />
<p>


=head2 violin

plot a hash of array refs as violins

=head3 options

=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>color</code></td><td># a hash, where keys are the keys in data, and values are colors, e.g. X => 'blue'</td><td></td></tr>
<tr><td><code>colors</code></td><td>match sets</td><td><code>colors       => { E => 'yellow', B => 'purple', A => 'green' }</code></td></tr>
<tr><td><code>key.order</code></td><td>determine key order display on x-axis</td><td></td></tr>
<tr><td><code>log</code></td><td># if set to > 1, the y-axis will be logarithmic</td><td></td></tr>
<tr><td><code>orientation</code></td><td>'vertical', 'horizontal'}, default: 'vertical'</td><td></td></tr>
</tbody>
</table>

=head3 single, simple plot

 plt({
     'output.file' => 'output.images/single.violinplot.png',
     data              => {                                     # simple hash
         A => [ 55, @{$z} ],
         E => [ @{$y} ],
         B => [ 122, @{$z} ],
     },
     'plot.type'  => 'violinplot',
     title        => 'Single Violin Plot: Specified Colors',
     colors       => { E => 'yellow', B => 'purple', A => 'green' },
     fh => $fh,
     execute      => 0,
 });

which makes:


=for html
<p>
<img width="651" height="491" alt="single violinplot" src="https://github.com/user-attachments/assets/989650fd-c947-45b0-91c8-c7f71c075cf3" />
<p>


=head3 multiple plots

 plt({
     fh                => $fh,
     execute           => 0,
     'output.file'     => 'output.images/violin.png',
     plots             => [
         {
             data => {
                 E => @e,
                 B => @b
             },
             'plot.type'  => 'violinplot',
             title        => 'Basic',
             xlabel       => 'xlabel',
             set_figwidth => 12,
             suptitle     => 'Violinplot'
         },
         {
             data => {
                 E => @e,
                 B => @b
             },
             'plot.type' => 'violinplot',
             color       => 'red',
             title       => 'Set Same Color for All',
         },
         {
             data => {
                 E => @e,
                 B => @b
             },
             'plot.type' => 'violinplot',
             colors      => {
                 E => 'yellow',
                 B => 'black'
             },
             title => 'Color by Key',
         },
         {
             data => {
                 E => @e,
                 B => @b
             },
             orientation => 'horizontal',
             'plot.type' => 'violinplot',
             colors      => {
                 E => 'yellow',
                 B => 'black'
             },
             title => 'Horizontal orientation',
         },
         {
             data => {
                 E => @e,
                 B => @b
             },
             whiskers    => 0,
             'plot.type' => 'violinplot',
             colors      => {
                 E => 'yellow',
                 B => 'black'
             },
             title => 'Whiskers off',
         },
     ],
     ncols => 3,
     nrows => 2,
 });


=for html
<p>
<img width="1211" height="491" alt="violin" src="https://github.com/user-attachments/assets/248df5e4-fd57-45d6-96da-956af0a7dbfb" />
<p>


=head2 wide

=head3 options

=head3 single, simple plot

=head3 multiple plots

=head1 Advanced

=head2 Notes in Files

all files that can have notes with them, give notes about how the file was written.  For example, SVG files have the following:

 <dc:title>made/written by /mnt/ceph/dcondon/ui/gromacs/tut/dup.2puy/1.plot.gromacs.pl called using "plot" in /mnt/ceph/dcondon/perl5/perlbrew/perls/perl-5.42.0/lib/site_perl/5.42.0/x86_64-linux/Matplotlib/Simple.pm</dc:title>`

=head2 Speed

To improve speed, all data can be written into a single temp python3 file thus:

 use File::Temp;
 my $fh = File::Temp->new( DIR => '/tmp', SUFFIX => '.py', UNLINK => 0 );

all files will be written to C<< $fh-E<gt>filename >>; be sure to put C<< execute =E<gt> 0 >> unless you want the file to be run, which is the last step.

 plt({
     data => {
         Clinical => [
             [
                 [@xw],    # x
                 [@y]      # y
             ],
             [ [@xw], [ map { $_ + rand_between( -0.5, 0.5 ) } @y ] ],
             [ [@xw], [ map { $_ + rand_between( -0.5, 0.5 ) } @y ] ]
         ],
         HGI => [
             [
                 [@xw],                            # x
                 [ map { 1.9 - 1.1 / $_ } @xw ]    # y
             ],
             [ [@xw], [ map { $_ + rand_between( -0.5, 0.5 ) } @y ] ],
             [ [@xw], [ map { $_ + rand_between( -0.5, 0.5 ) } @y ] ]
         ]
     },
     'output.file' => 'output.images/single.wide.png',
     'plot.type'       => 'wide',
     color             => {
         Clinical => 'blue',
         HGI      => 'green'
     },
     title        => 'Visualization of similar lines plotted together',
     fh => $fh,
     execute      => 0,
 });
 # the last plot should have C<< execute =E<gt> 1 >>
 plt({
     data => [
         [
             [@xw],    # x
             [@y]      # y
         ],
         [ [@xw], [ map { $_ + rand_between( -0.5, 0.5 ) } @y ] ],
         [ [@xw], [ map { $_ + rand_between( -0.5, 0.5 ) } @y ] ]
     ],
     'output.file' => 'output.images/single.array.png',
     'plot.type'       => 'wide',
     color             => 'red',
     title             => 'Visualization of similar lines plotted together',
     fh                => $fh,
     execute           => 1,
 });
