# HG changeset patch
# User Pantxo Diribarne <pantxo.diribarne@gmail.com>
# Date 1637946367 -3600
#      Fri Nov 26 18:06:07 2021 +0100
# Node ID 167e020bbb35c88cd3f09f159f78cdb55c867e93
# Parent  1b14241560fa20d22e5ae46d3c725d1dac4334c4
[mq]: limitmethod

diff -r 1b14241560fa -r 167e020bbb35 libinterp/corefcn/graphics.cc
--- a/libinterp/corefcn/graphics.cc	Thu Nov 25 15:29:20 2021 +0100
+++ b/libinterp/corefcn/graphics.cc	Fri Nov 26 18:06:07 2021 +0100
@@ -7785,7 +7785,8 @@
 Matrix
 axes::properties::get_axis_limits (double xmin, double xmax,
                                    double min_pos, double max_neg,
-                                   const bool logscale)
+                                   const bool logscale,
+                                   const std::string& method)
 {
   Matrix retval;
 
@@ -7837,17 +7838,38 @@
                   max_val *= 0.9;
                 }
             }
-          if (min_val > 0)
-            {
-              // Log plots with all positive data
-              min_val = std::pow (10, std::floor (log10 (min_val)));
-              max_val = std::pow (10, std::ceil (log10 (max_val)));
-            }
-          else
-            {
-              // Log plots with all negative data
-              min_val = -std::pow (10, std::ceil (log10 (-min_val)));
-              max_val = -std::pow (10, std::floor (log10 (-max_val)));
+
+          if (method == "tickaligned")
+            {
+              if (min_val > 0)
+                {
+                  // Log plots with all positive data
+                  min_val = std::pow (10, std::floor (log10 (min_val)));
+                  max_val = std::pow (10, std::ceil (log10 (max_val)));
+                }
+              else
+                {
+                  // Log plots with all negative data
+                  min_val = -std::pow (10, std::ceil (log10 (-min_val)));
+                  max_val = -std::pow (10, std::floor (log10 (-max_val)));
+                }
+            }
+          else if (method == "padded")
+            {
+              if (min_val > 0)
+                {
+                  // Log plots with all positive data
+                  double pad = (log10 (max_val) - log10 (min_val)) * 0.035;
+                  min_val = std::pow (10, log10 (min_val) - pad);
+                  max_val = std::pow (10, log10 (max_val) + pad);
+                }
+              else
+                {
+                  // Log plots with all negative data
+                  double pad = (log10 (-min_val) - log10 (-max_val)) * 0.035;
+                  min_val = -std::pow (10, log10 (-min_val) + pad);
+                  max_val = -std::pow (10, log10 (-max_val) - pad);
+                }
             }
         }
       else
@@ -7865,12 +7887,21 @@
               max_val += 0.1 * std::abs (max_val);
             }
 
-          double tick_sep = calc_tick_sep (min_val, max_val);
-          double min_tick = std::floor (min_val / tick_sep);
-          double max_tick = std::ceil (max_val / tick_sep);
-          // Prevent round-off from cropping ticks
-          min_val = std::min (min_val, tick_sep * min_tick);
-          max_val = std::max (max_val, tick_sep * max_tick);
+          if (method == "tickaligned")
+            {
+              double tick_sep = calc_tick_sep (min_val, max_val);
+              double min_tick = std::floor (min_val / tick_sep);
+              double max_tick = std::ceil (max_val / tick_sep);
+              // Prevent round-off from cropping ticks
+              min_val = std::min (min_val, tick_sep * min_tick);
+              max_val = std::max (max_val, tick_sep * max_tick);
+            }
+          else if (method == "padded")
+            {
+              double pad = 0.035 * (max_val - min_val);
+              min_val -= pad;
+              max_val += pad;
+            }
         }
     }
 
@@ -8085,13 +8116,16 @@
                                        array_property& mticks,
                                        bool limmode_is_auto,
                                        bool tickmode_is_auto,
-                                       bool is_logscale)
+                                       bool is_logscale,
+                                       bool method_is_padded,
+                                       bool method_is_tight)
 {
   if (lims.get ().isempty ())
     return;
 
   double lo = (lims.get ().matrix_value ())(0);
   double hi = (lims.get ().matrix_value ())(1);
+
   double lo_lim = lo;
   double hi_lim = hi;
   bool is_negative = lo < 0 && hi < 0;
@@ -8135,17 +8169,28 @@
 
       if (limmode_is_auto)
         {
-          // Adjust limits to include min and max ticks
           Matrix tmp_lims (1,2);
-          tmp_lims(0) = std::min (tick_sep * i1, lo);
-          tmp_lims(1) = std::max (tick_sep * i2, hi);
+
+          if (! method_is_padded && ! method_is_tight)
+            {
+              // Adjust limits to include min and max ticks
+              tmp_lims(0) = std::min (tick_sep * i1, lo);
+              tmp_lims(1) = std::max (tick_sep * i2, hi);
+            }
+          else
+            {
+              tmp_lims(0) = lo;
+              tmp_lims(1) = hi;
+            }
 
           if (is_logscale)
             {
               tmp_lims(0) = std::pow (10., tmp_lims(0));
               tmp_lims(1) = std::pow (10., tmp_lims(1));
+
               if (tmp_lims(0) <= 0)
                 tmp_lims(0) = std::pow (10., lo);
+
               if (is_negative)
                 {
                   double tmp = tmp_lims(0);
@@ -8153,6 +8198,7 @@
                   tmp_lims(1) = -tmp;
                 }
             }
+
           lims = tmp_lims;
         }
       else
@@ -8521,9 +8567,11 @@
         {
           get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'x');
 
+          std::string method = m_properties.get_xlimitmethod ();
           limits = m_properties.get_axis_limits (min_val, max_val,
                                                  min_pos, max_neg,
-                                                 m_properties.xscale_is ("log"));
+                                                 m_properties.xscale_is ("log"),
+                                                 method);
         }
       else
         m_properties.check_axis_limits (limits, kids,
@@ -8542,9 +8590,11 @@
         {
           get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'y');
 
+          std::string method = m_properties.get_ylimitmethod ();
           limits = m_properties.get_axis_limits (min_val, max_val,
                                                  min_pos, max_neg,
-                                                 m_properties.yscale_is ("log"));
+                                                 m_properties.yscale_is ("log"),
+                                                 method);
         }
       else
         m_properties.check_axis_limits (limits, kids,
@@ -8566,9 +8616,11 @@
           m_properties.set_has3Dkids ((max_val - min_val) >
                                       std::numeric_limits<double>::epsilon ());
 
+          std::string method = m_properties.get_zlimitmethod ();
           limits = m_properties.get_axis_limits (min_val, max_val,
                                                  min_pos, max_neg,
-                                                 m_properties.zscale_is ("log"));
+                                                 m_properties.zscale_is ("log"),
+                                                 method);
         }
       else
         {
@@ -8721,9 +8773,11 @@
         {
           get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'x');
 
+          std::string method = m_properties.get_xlimitmethod ();
           limits = m_properties.get_axis_limits (min_val, max_val,
                                                  min_pos, max_neg,
-                                                 m_properties.xscale_is ("log"));
+                                                 m_properties.xscale_is ("log"),
+                                                 method);
         }
       else
         {
@@ -8744,9 +8798,11 @@
         {
           get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'y');
 
+          std::string method = m_properties.get_ylimitmethod ();
           limits = m_properties.get_axis_limits (min_val, max_val,
                                                  min_pos, max_neg,
-                                                 m_properties.yscale_is ("log"));
+                                                 m_properties.yscale_is ("log"),
+                                                 method);
         }
       else
         {
@@ -8776,9 +8832,11 @@
               && ! m_properties.zscale_is ("log"))
             min_val = max_val = 0.;
 
+          std::string method = m_properties.get_zlimitmethod ();
           limits = m_properties.get_axis_limits (min_val, max_val,
                                                  min_pos, max_neg,
-                                                 m_properties.zscale_is ("log"));
+                                                 m_properties.zscale_is ("log"),
+                                                 method);
         }
       else
         {
diff -r 1b14241560fa -r 167e020bbb35 libinterp/corefcn/graphics.in.h
--- a/libinterp/corefcn/graphics.in.h	Thu Nov 25 15:29:20 2021 +0100
+++ b/libinterp/corefcn/graphics.in.h	Fri Nov 26 18:06:07 2021 +0100
@@ -3828,6 +3828,7 @@
       bool_property xgrid , "off"
       handle_property xlabel SOf , make_graphics_handle ("text", m___myhandle__, false, false, false)
       row_vector_property xlim mu , default_lim ()
+      radio_property xlimitmethod u , "{tickaligned}|tight|padded"
       radio_property xlimmode al , "{auto}|manual"
       bool_property xminorgrid , "off"
       bool_property xminortick , "off"
@@ -3847,6 +3848,7 @@
       bool_property ygrid , "off"
       handle_property ylabel SOf , make_graphics_handle ("text", m___myhandle__, false, false, false)
       row_vector_property ylim mu , default_lim ()
+      radio_property ylimitmethod u , "{tickaligned}|tight|padded"
       radio_property ylimmode al , "{auto}|manual"
       bool_property yminorgrid , "off"
       bool_property yminortick , "off"
@@ -3864,6 +3866,7 @@
       bool_property zgrid , "off"
       handle_property zlabel SOf , make_graphics_handle ("text", m___myhandle__, false, false, false)
       row_vector_property zlim mu , default_lim ()
+      radio_property zlimitmethod u , "{tickaligned}|tight|padded"
       radio_property zlimmode al , "{auto}|manual"
       bool_property zminorgrid , "off"
       bool_property zminortick , "off"
@@ -4043,7 +4046,8 @@
     {
       calc_ticks_and_lims (m_xlim, m_xtick, m_xminortickvalues,
                            m_xlimmode.is ("auto"), m_xtickmode.is ("auto"),
-                           m_xscale.is ("log"));
+                           m_xscale.is ("log"), m_xlimitmethod.is ("padded"),
+                           m_xlimitmethod.is ("tight"));
       if (m_xticklabelmode.is ("auto"))
         calc_ticklabels (m_xtick, m_xticklabel, m_xscale.is ("log"),
                          xaxislocation_is ("origin"),
@@ -4059,7 +4063,8 @@
     {
       calc_ticks_and_lims (m_ylim, m_ytick, m_yminortickvalues,
                            m_ylimmode.is ("auto"), m_ytickmode.is ("auto"),
-                           m_yscale.is ("log"));
+                           m_yscale.is ("log"), m_ylimitmethod.is ("padded"),
+                           m_ylimitmethod.is ("tight"));
       if (m_yticklabelmode.is ("auto"))
         calc_ticklabels (m_ytick, m_yticklabel, m_yscale.is ("log"),
                          yaxislocation_is ("origin"),
@@ -4075,7 +4080,8 @@
     {
       calc_ticks_and_lims (m_zlim, m_ztick, m_zminortickvalues,
                            m_zlimmode.is ("auto"), m_ztickmode.is ("auto"),
-                           m_zscale.is ("log"));
+                           m_zscale.is ("log"), m_zlimitmethod.is ("padded"),
+                           m_zlimitmethod.is ("tight"));
       if (m_zticklabelmode.is ("auto"))
         calc_ticklabels (m_ztick, m_zticklabel, m_zscale.is ("log"), false,
                          2, m_zlim);
@@ -4180,7 +4186,8 @@
     OCTINTERP_API void
     calc_ticks_and_lims (array_property& lims, array_property& ticks,
                          array_property& mticks, bool limmode_is_auto,
-                         bool tickmode_is_auto, bool is_logscale);
+                         bool tickmode_is_auto, bool is_logscale,
+                         bool method_is_padded, bool method_is_tight);
     OCTINTERP_API void
     calc_ticklabels (const array_property& ticks, any_property& labels,
                      bool is_logscale, const bool is_origin,
@@ -4229,7 +4236,7 @@
     OCTINTERP_API Matrix
     get_axis_limits (double xmin, double xmax,
                      double min_pos, double max_neg,
-                     const bool logscale);
+                     const bool logscale, const std::string& method);
 
     OCTINTERP_API void
     check_axis_limits (Matrix &limits, const Matrix kids,
@@ -4241,7 +4248,8 @@
 
       calc_ticks_and_lims (m_xlim, m_xtick, m_xminortickvalues,
                            m_xlimmode.is ("auto"), m_xtickmode.is ("auto"),
-                           m_xscale.is ("log"));
+                           m_xscale.is ("log"), m_xlimitmethod.is ("padded"),
+                           m_xlimitmethod.is ("tight"));
       if (m_xticklabelmode.is ("auto"))
         calc_ticklabels (m_xtick, m_xticklabel, m_xscale.is ("log"),
                          m_xaxislocation.is ("origin"),
@@ -4257,13 +4265,19 @@
       update_axes_layout ();
     }
 
+    void update_xlimitmethod ()
+    {
+      update_xlim ();
+    }
+
     void update_ylim (void)
     {
       update_axis_limits ("ylim");
 
       calc_ticks_and_lims (m_ylim, m_ytick, m_yminortickvalues,
                            m_ylimmode.is ("auto"), m_ytickmode.is ("auto"),
-                           m_yscale.is ("log"));
+                           m_yscale.is ("log"), m_ylimitmethod.is ("padded"),
+                           m_ylimitmethod.is ("tight"));
       if (m_yticklabelmode.is ("auto"))
         calc_ticklabels (m_ytick, m_yticklabel, m_yscale.is ("log"),
                          yaxislocation_is ("origin"),
@@ -4279,13 +4293,19 @@
       update_axes_layout ();
     }
 
+    void update_ylimitmethod ()
+    {
+      update_ylim ();
+    }
+
     void update_zlim (void)
     {
       update_axis_limits ("zlim");
 
       calc_ticks_and_lims (m_zlim, m_ztick, m_zminortickvalues,
                            m_zlimmode.is ("auto"), m_ztickmode.is ("auto"),
-                           m_zscale.is ("log"));
+                           m_zscale.is ("log"), m_zlimitmethod.is ("padded"),
+                           m_zlimitmethod.is ("tight"));
       if (m_zticklabelmode.is ("auto"))
         calc_ticklabels (m_ztick, m_zticklabel, m_zscale.is ("log"), false,
                          2, m_zlim);
@@ -4297,6 +4317,11 @@
       update_axes_layout ();
     }
 
+    void update_zlimitmethod ()
+    {
+      update_zlim ();
+    }
+
     void trigger_normals_calc (void);
 
   };
