# HG changeset patch
# User Markus Mützel <markus.muetzel@gmx.de>
# Date 1497987033 -7200
#      Tue Jun 20 21:30:33 2017 +0200
# Node ID 37772ab64a39dba872d6302f21bcb5ff1d00ee4f
# Parent  6fee94b27af1d172b49eb68ee84595c43bdc83ae
Auto-adjust limits that span 0 on logarithmic axes (bug #43606).

* graphics.in.h (update_[xyz]lim): Check limits on update.

* graphics.cc (get_axis_limits): Change Inf limits to tight limits. Check manual
limits on logarithmic axes.

* __axis_limits__.m: Remove check for Inf from high level function.

diff -r 6fee94b27af1 -r 37772ab64a39 libinterp/corefcn/graphics.cc
--- a/libinterp/corefcn/graphics.cc	Tue Jun 20 09:10:59 2017 -0700
+++ b/libinterp/corefcn/graphics.cc	Tue Jun 20 21:30:33 2017 +0200
@@ -6984,101 +6984,143 @@
 // Attempt to make "nice" limits from the actual max and min of the data.
 // For log plots, we will also use the smallest strictly positive value.
 
-Matrix
-axes::properties::get_axis_limits (double xmin, double xmax,
+void
+axes::properties::get_axis_limits (Matrix &limits, double xmin, double xmax,
                                    double min_pos, double max_neg,
-                                   bool logscale)
-{
-  Matrix retval;
-
+                                   const bool logscale, const bool autoscale,
+                                   char &update_type)
+{
   double min_val = xmin;
   double max_val = xmax;
-
-  if (octave::math::isinf (min_val) && min_val > 0
-      && octave::math::isinf (max_val) && max_val < 0)
-    {
-      retval = default_lim (logscale);
-      return retval;
-    }
-  else if (! (octave::math::isinf (min_val) || octave::math::isinf (max_val)))
-    {
-      if (logscale)
-        {
-          if (octave::math::isinf (min_pos) && octave::math::isinf (max_neg))
+  bool do_update = false;
+
+  if (autoscale)
+    {
+
+      if (octave::math::isinf (min_val) && min_val > 0
+          && octave::math::isinf (max_val) && max_val < 0)
+        {
+          limits = default_lim (logscale);
+          return;
+        }
+      else if (! (octave::math::isinf (min_val) ||
+                  octave::math::isinf (max_val)))
+        {
+          if (logscale)
             {
-              // FIXME: max_neg is needed for "loglog ([0 -Inf])"
-              //        This is the *only* place where max_neg is needed.
-              //        Is there another way?
-              retval = default_lim ();
-              retval(0) = std::pow (10., retval(0));
-              retval(1) = std::pow (10., retval(1));
-              return retval;
-            }
-          if (min_val <= 0 && max_val > 0)
-            {
-              warning_with_id ("Octave:negative-data-log-axis",
-                               "axis: omitting non-positive data in log plot");
-              min_val = min_pos;
-            }
-          // FIXME: maybe this test should also be relative?
-          if (std::abs (min_val - max_val)
-              < sqrt (std::numeric_limits<double>::epsilon ()))
-            {
-              // Widen range when too small
-              if (min_val >= 0)
+              if (octave::math::isinf (min_pos) &&
+                  octave::math::isinf (max_neg))
                 {
-                  min_val *= 0.9;
-                  max_val *= 1.1;
+                  // FIXME: max_neg is needed for "loglog ([0 -Inf])"
+                  //        This is the *only* place where max_neg is needed.
+                  //        Is there another way?
+                  limits = default_lim ();
+                  limits(0) = std::pow (10., limits(0));
+                  limits(1) = std::pow (10., limits(1));
+                  return;
+                }
+              if (min_val <= 0 && max_val > 0)
+                {
+                  warning_with_id ("Octave:negative-data-log-axis",
+                                   "axis: omitting non-positive data in log plot");
+                  min_val = min_pos;
+                }
+              // FIXME: maybe this test should also be relative?
+              if (std::abs (min_val - max_val)
+                  < sqrt (std::numeric_limits<double>::epsilon ()))
+                {
+                  // Widen range when too small
+                  if (min_val >= 0)
+                    {
+                      min_val *= 0.9;
+                      max_val *= 1.1;
+                    }
+                  else
+                    {
+                      min_val *= 1.1;
+                      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
                 {
-                  min_val *= 1.1;
-                  max_val *= 0.9;
+                  // 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 (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 (min_val == 0 && max_val == 0)
+                {
+                  min_val = -1;
+                  max_val = 1;
+                }
+              // FIXME: maybe this test should also be relative?
+              else if (std::abs (min_val - max_val)
+                       < sqrt (std::numeric_limits<double>::epsilon ()))
+                {
+                  min_val -= 0.1 * std::abs (min_val);
+                  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);
             }
         }
-      else
-        {
-          if (min_val == 0 && max_val == 0)
-            {
-              min_val = -1;
-              max_val = 1;
-            }
-          // FIXME: maybe this test should also be relative?
-          else if (std::abs (min_val - max_val)
-                   < sqrt (std::numeric_limits<double>::epsilon ()))
+
+      limits.resize (1, 2);
+
+      limits(0) = min_val;
+      limits(1) = max_val;
+
+      return;
+    }
+  else
+    {
+      if (! octave::math::isfinite (limits(0)))
+        {
+          limits(0) = min_val;
+          do_update = true;
+        }
+      if (! octave::math::isfinite (limits(1)))
+        {
+          limits(1) = max_val;
+          do_update = true;
+        }
+
+      if (logscale && limits(0)*limits(1) <= 0)
+        {
+          if (max_val > 0)
             {
-              min_val -= 0.1 * std::abs (min_val);
-              max_val += 0.1 * std::abs (max_val);
+              warning_with_id ("octave:axis-non-positive-log-limits",
+                               "Non-positive limit for logarithmic axis ignored.\n");
+              limits(0) = min_pos;
+              do_update = true;
             }
 
-          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);
-        }
-    }
-
-  retval.resize (1, 2);
-
-  retval(0) = min_val;
-  retval(1) = max_val;
-
-  return retval;
+          else
+            {
+              warning_with_id ("octave:axis-non-negative-log-limits",
+                               "Non-negative limit for logarithmic axis ignored.\n");
+              limits(1) = max_neg;
+              do_update = true;
+            }
+        }
+    }
+
+  if (! do_update)
+    update_type = 0;
+
 }
 
 void
@@ -7527,55 +7569,47 @@
       || axis_type == "xlimmode" || axis_type == "xliminclude"
       || axis_type == "xlim")
     {
-      if (xproperties.xlimmode_is ("auto"))
-        {
-          limits = xproperties.get_xlim ().matrix_value ();
-          FIX_LIMITS;
-
-          get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'x');
-
-          limits = xproperties.get_axis_limits (min_val, max_val,
-                                                min_pos, max_neg,
-                                                xproperties.xscale_is ("log"));
-
-          update_type = 'x';
-        }
+      limits = xproperties.get_xlim ().matrix_value ();
+      FIX_LIMITS;
+
+      get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'x');
+
+      update_type = 'x';
+      xproperties.get_axis_limits (limits, min_val, max_val, min_pos, max_neg,
+                                   xproperties.xscale_is ("log"),
+                                   xproperties.xlimmode_is ("auto"),
+                                   update_type);
+
     }
   else if (axis_type == "ydata" || axis_type == "yscale"
            || axis_type == "ylimmode" || axis_type == "yliminclude"
            || axis_type == "ylim")
     {
-      if (xproperties.ylimmode_is ("auto"))
-        {
-          limits = xproperties.get_ylim ().matrix_value ();
-          FIX_LIMITS;
-
-          get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'y');
-
-          limits = xproperties.get_axis_limits (min_val, max_val,
-                                                min_pos, max_neg,
-                                                xproperties.yscale_is ("log"));
-
-          update_type = 'y';
-        }
+      limits = xproperties.get_ylim ().matrix_value ();
+      FIX_LIMITS;
+
+      get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'y');
+
+      update_type = 'y';
+      xproperties.get_axis_limits (limits, min_val, max_val, min_pos, max_neg,
+                                   xproperties.yscale_is ("log"),
+                                   xproperties.ylimmode_is ("auto"),
+                                   update_type);
     }
   else if (axis_type == "zdata" || axis_type == "zscale"
            || axis_type == "zlimmode" || axis_type == "zliminclude"
            || axis_type == "zlim")
     {
-      if (xproperties.zlimmode_is ("auto"))
-        {
-          limits = xproperties.get_zlim ().matrix_value ();
-          FIX_LIMITS;
-
-          get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'z');
-
-          limits = xproperties.get_axis_limits (min_val, max_val,
-                                                min_pos, max_neg,
-                                                xproperties.zscale_is ("log"));
-
-          update_type = 'z';
-        }
+      limits = xproperties.get_zlim ().matrix_value ();
+      FIX_LIMITS;
+
+      get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'z');
+
+      update_type = 'z';
+      xproperties.get_axis_limits (limits, min_val, max_val, min_pos, max_neg,
+                                   xproperties.zscale_is ("log"),
+                                   xproperties.zlimmode_is ("auto"),
+                                   update_type);
     }
   else if (axis_type == "cdata" || axis_type == "climmode"
            || axis_type == "cdatamapping" || axis_type == "climinclude"
@@ -7643,24 +7677,31 @@
   frame.protect_var (updating_axis_limits);
 
   updating_axis_limits.insert (get_handle ().value ());
+  bool is_auto;
 
   switch (update_type)
     {
     case 'x':
+      is_auto = xproperties.xlimmode_is ("auto");
       xproperties.set_xlim (limits);
-      xproperties.set_xlimmode ("auto");
+      if (is_auto)
+        xproperties.set_xlimmode ("auto");
       xproperties.update_xlim ();
       break;
 
     case 'y':
+      is_auto = xproperties.ylimmode_is ("auto");
       xproperties.set_ylim (limits);
-      xproperties.set_ylimmode ("auto");
+      if (is_auto)
+        xproperties.set_ylimmode ("auto");
       xproperties.update_ylim ();
       break;
 
     case 'z':
+      is_auto = xproperties.zlimmode_is ("auto");
       xproperties.set_zlim (limits);
-      xproperties.set_zlimmode ("auto");
+      if (is_auto)
+        xproperties.set_zlimmode ("auto");
       xproperties.update_zlim ();
       break;
 
@@ -7705,46 +7746,40 @@
       || axis_type == "xlimmode" || axis_type == "xliminclude"
       || axis_type == "xlim")
     {
-      if (xproperties.xlimmode_is ("auto"))
-        {
-          get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'x');
-
-          limits = xproperties.get_axis_limits (min_val, max_val,
-                                                min_pos, max_neg,
-                                                xproperties.xscale_is ("log"));
-
-          update_type = 'x';
-        }
+      get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'x');
+
+      limits = xproperties.get_xlim ().matrix_value ();
+      update_type = 'x';
+      xproperties.get_axis_limits (limits, min_val, max_val, min_pos, max_neg,
+                                   xproperties.xscale_is ("log"),
+                                   xproperties.xlimmode_is ("auto"),
+                                   update_type);
     }
   else if (axis_type == "ydata" || axis_type == "yscale"
            || axis_type == "ylimmode" || axis_type == "yliminclude"
            || axis_type == "ylim")
     {
-      if (xproperties.ylimmode_is ("auto"))
-        {
-          get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'y');
-
-          limits = xproperties.get_axis_limits (min_val, max_val,
-                                                min_pos, max_neg,
-                                                xproperties.yscale_is ("log"));
-
-          update_type = 'y';
-        }
+      get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'y');
+
+      limits = xproperties.get_ylim ().matrix_value ();
+      update_type = 'y';
+      xproperties.get_axis_limits (limits, min_val, max_val, min_pos, max_neg,
+                                   xproperties.yscale_is ("log"),
+                                   xproperties.ylimmode_is ("auto"),
+                                   update_type);
     }
   else if (axis_type == "zdata" || axis_type == "zscale"
            || axis_type == "zlimmode" || axis_type == "zliminclude"
            || axis_type == "zlim")
     {
-      if (xproperties.zlimmode_is ("auto"))
-        {
-          get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'z');
-
-          limits = xproperties.get_axis_limits (min_val, max_val,
-                                                min_pos, max_neg,
-                                                xproperties.zscale_is ("log"));
-
-          update_type = 'z';
-        }
+      get_children_limits (min_val, max_val, min_pos, max_neg, kids, 'z');
+
+      limits = xproperties.get_zlim ().matrix_value ();
+      update_type = 'z';
+      xproperties.get_axis_limits (limits, min_val, max_val, min_pos, max_neg,
+                                   xproperties.zscale_is ("log"),
+                                   xproperties.zlimmode_is ("auto"),
+                                   update_type);
     }
   else if (axis_type == "cdata" || axis_type == "climmode"
            || axis_type == "cdatamapping" || axis_type == "climinclude"
@@ -7804,24 +7839,31 @@
   frame.protect_var (updating_axis_limits);
 
   updating_axis_limits.insert (get_handle ().value ());
+  bool is_auto;
 
   switch (update_type)
     {
     case 'x':
+      is_auto = xproperties.xlimmode_is ("auto");
       xproperties.set_xlim (limits);
-      xproperties.set_xlimmode ("auto");
+      if (is_auto)
+        xproperties.set_xlimmode ("auto");
       xproperties.update_xlim ();
       break;
 
     case 'y':
+      is_auto = xproperties.ylimmode_is ("auto");
       xproperties.set_ylim (limits);
-      xproperties.set_ylimmode ("auto");
+      if (is_auto)
+        xproperties.set_ylimmode ("auto");
       xproperties.update_ylim ();
       break;
 
     case 'z':
+      is_auto = xproperties.zlimmode_is ("auto");
       xproperties.set_zlim (limits);
-      xproperties.set_zlimmode ("auto");
+      if (is_auto)
+        xproperties.set_zlimmode ("auto");
       xproperties.update_zlim ();
       break;
 
diff -r 6fee94b27af1 -r 37772ab64a39 libinterp/corefcn/graphics.in.h
--- a/libinterp/corefcn/graphics.in.h	Tue Jun 20 09:10:59 2017 -0700
+++ b/libinterp/corefcn/graphics.in.h	Tue Jun 20 21:30:33 2017 +0200
@@ -4458,12 +4458,15 @@
     octave_value get_colormap (void) const;
 
   public:
-    Matrix get_axis_limits (double xmin, double xmax,
-                            double min_pos, double max_neg,
-                            bool logscale);
+    void get_axis_limits (Matrix &limits, double xmin, double xmax,
+                          double min_pos, double max_neg,
+                          const bool logscale, const bool autoscale,
+                          char &update_type);
 
     void update_xlim ()
     {
+      update_axis_limits ("xlim");
+
       calc_ticks_and_lims (xlim, xtick, xminortickvalues, xlimmode.is ("auto"),
                            xtickmode.is ("auto"), xscale.is ("log"));
       if (xticklabelmode.is ("auto"))
@@ -4483,6 +4486,8 @@
 
     void update_ylim (void)
     {
+      update_axis_limits ("ylim");
+
       calc_ticks_and_lims (ylim, ytick, yminortickvalues, ylimmode.is ("auto"),
                            ytickmode.is ("auto"), yscale.is ("log"));
       if (yticklabelmode.is ("auto"))
@@ -4502,6 +4507,8 @@
 
     void update_zlim (void)
     {
+      update_axis_limits ("zlim");
+
       calc_ticks_and_lims (zlim, ztick, zminortickvalues, zlimmode.is ("auto"),
                            ztickmode.is ("auto"), zscale.is ("log"));
       if (zticklabelmode.is ("auto"))
diff -r 6fee94b27af1 -r 37772ab64a39 scripts/plot/appearance/private/__axis_limits__.m
--- a/scripts/plot/appearance/private/__axis_limits__.m	Tue Jun 20 09:10:59 2017 -0700
+++ b/scripts/plot/appearance/private/__axis_limits__.m	Tue Jun 20 21:30:33 2017 +0200
@@ -44,17 +44,10 @@
     else
       if (! isnumeric (arg) && any (size (arg(:)) != [2, 1]))
         error ("%s: LIMITS must be a 2-element vector", fcn);
+      elseif (arg(1) >= arg(2))
+        error ("%s: axis limits must be increasing", fcn);
       else
-        if (arg(1) >= arg(2))
-          error ("%s: axis limits must be increasing", fcn);
-        else
-          autoscale = isinf (arg);
-          if (any (autoscale))
-            set (hax, fcnmode, "auto");
-            arg(autoscale) = get (hax, fcn)(autoscale);
-          endif
-          set (hax, fcn, arg(:));
-        endif
+        set (hax, fcn, arg(:));
       endif
     endif
   endif
