changeset 30659:4c0c02102ba9

Allow for sticky "tight" option for auto axes limits (bug #61526) * graphics.in.h (axes::properties::m_xlimitmethod, m_ylimitmethod, m_zlimitmethod): New properties with updaters. (axes::properties::update_xlimitmethod, update_ylimitmethod, update_zlimitmethod): New updater methods that call corresponding update_*lim. (axes::properties::calc_ticks_and_lims): Change signature to include two new bool arguments that indicate whether limitmethod is tight or padded. Change all uses. (axes::properties::get_axis_limits): Change signature to include limitmethod as a string. * graphics.in.h (axes::properties::calc_ticks_and_lims): Don't adjust limits to closest ticks for "tight" and "padded" methods. (axes::properties::get_axis_limits): Leave limits unchanged for "tigh" method or add a 7% padding for "padded" method. * axis.m: Make use of the new properties and remove custom limits calculation routines, __get_tight_lims__ and __do_tight_option__. Add new options "padded" and "tickaligned". Add two demos to show the three methods in linear and log coordinates. Add test for stickiness of the "tight" option. * genpropdoc.m: Document new properties.
author Pantxo Diribarne <pantxo.diribarne@gmail.com>
date Fri, 26 Nov 2021 18:06:07 +0100
parents 2434363ba336
children dfd2f1db0291
files doc/interpreter/genpropdoc.m libinterp/corefcn/graphics.cc libinterp/corefcn/graphics.in.h scripts/plot/appearance/axis.m
diffstat 4 files changed, 220 insertions(+), 121 deletions(-) [+]
line wrap: on
line diff
--- a/doc/interpreter/genpropdoc.m	Fri Jan 21 15:23:38 2022 +0100
+++ b/doc/interpreter/genpropdoc.m	Fri Nov 26 18:06:07 2021 +0100
@@ -930,6 +930,15 @@
 for the x-axis.  __modemsg__.   @xref{XREFxlim, , @w{xlim function}}.";
         s.valid = valid_2elvec;
 
+      case "xlimitmethod"
+        s.doc = "Method used to determine the x axis limits when the \
+@code{xlimmode} property is @qcode{\"auto\"}.  The default value, \
+@qcode{\"tickaligned\"} makes limits align with the closest ticks.  With \
+value @qcode{\"tight\"} the limits are adjusted to enclose all the graphics \
+objects in the axes, while with value @qcode{\"padded\"}, an additionnal \
+margin of about 7%% of the data extent is added around the objects. \
+@xref{XREFaxis, , @w{axis function}}.";
+
       case "xlimmode"
       case "xminorgrid"
         s.doc = "Control whether minor x grid lines are displayed.";
@@ -975,6 +984,15 @@
 for the y-axis.  __modemsg__.  @xref{XREFylim, , @w{ylim function}}.";
         s.valid = valid_2elvec;
 
+      case "xlimitmethod"
+        s.doc = "Method used to determine the y axis limits when the \
+@code{xlimmode} property is @qcode{\"auto\"}.  The default value, \
+@qcode{\"tickaligned\"} makes limits align with the closest ticks.  With \
+value @qcode{\"tight\"} the limits are adjusted to enclose all the graphics \
+objects in the axes, while with value @qcode{\"padded\"}, an additionnal \
+margin of about 7%% of the data extent is added around the objects. \
+@xref{XREFaxis, , @w{axis function}}.";
+
       case "ylimmode"
       case "yminorgrid"
         s.doc = "Control whether minor y grid lines are displayed.";
@@ -1013,6 +1031,15 @@
 for the z-axis.  __modemsg__.  @xref{XREFzlim, , @w{zlim function}}.";
         s.valid = valid_2elvec;
 
+      case "xlimitmethod"
+        s.doc = "Method used to determine the z axis limits when the \
+@code{xlimmode} property is @qcode{\"auto\"}.  The default value, \
+@qcode{\"tickaligned\"} makes limits align with the closest ticks.  With \
+value @qcode{\"tight\"} the limits are adjusted to enclose all the graphics \
+objects in the axes, while with value @qcode{\"padded\"}, an additionnal \
+margin of about 7%% of the data extent is added around the objects. \
+@xref{XREFaxis, , @w{axis function}}.";
+
       case "zlimmode"
       case "zminorgrid"
         s.doc = "Control whether minor z grid lines are displayed.";
--- a/libinterp/corefcn/graphics.cc	Fri Jan 21 15:23:38 2022 +0100
+++ b/libinterp/corefcn/graphics.cc	Fri Nov 26 18:06:07 2021 +0100
@@ -7786,7 +7786,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;
 
@@ -7838,17 +7839,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.07;
+                  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.07;
+                  min_val = -std::pow (10, log10 (-min_val) + pad);
+                  max_val = -std::pow (10, log10 (-max_val) - pad);
+                }
             }
         }
       else
@@ -7866,12 +7888,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.07 * (max_val - min_val);
+              min_val -= pad;
+              max_val += pad;
+            }
         }
     }
 
@@ -8086,13 +8117,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;
@@ -8136,17 +8170,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);
@@ -8154,6 +8199,7 @@
                   tmp_lims(1) = -tmp;
                 }
             }
+
           lims = tmp_lims;
         }
       else
@@ -8522,9 +8568,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,
@@ -8543,9 +8591,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,
@@ -8567,9 +8617,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
         {
@@ -8722,9 +8774,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
         {
@@ -8745,9 +8799,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
         {
@@ -8777,9 +8833,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
         {
--- a/libinterp/corefcn/graphics.in.h	Fri Jan 21 15:23:38 2022 +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);
 
   };
--- a/scripts/plot/appearance/axis.m	Fri Jan 21 15:23:38 2022 +0100
+++ b/scripts/plot/appearance/axis.m	Fri Nov 26 18:06:07 2021 +0100
@@ -83,9 +83,16 @@
 ## @item @qcode{"manual"}
 ## Fix the current axes limits.
 ##
+## @item @qcode{"tickaligned"}
+## Fix axes to the limits of the closest ticks.
+##
 ## @item @qcode{"tight"}
 ## Fix axes to the limits of the data.
 ##
+## @item @qcode{"padded"}
+## Fix axes to the limits of the data plus margins of about 7% of the
+## data extent.
+##
 ## @item @qcode{"image"}
 ## Equivalent to @qcode{"tight"} and @qcode{"equal"}.
 ##
@@ -213,8 +220,10 @@
       ## aspect ratio
       elseif (strcmpi (opt, "image"))
         __axis__ (ca, "equal");
-        set (ca, "plotboxaspectratiomode", "auto");
-        __do_tight_option__ (ca);
+        set (ca, "plotboxaspectratiomode", "auto", ...
+                 "xlimmode", "auto", "ylimmode", "auto", ...
+                 "zlimmode", "auto", ...
+                 "xlimitmethod", "tight", "ylimitmethod", "tight");
       elseif (strcmpi (opt, "square"))
         set (ca, "dataaspectratiomode", "auto",
                  "plotboxaspectratio", [1, 1, 1]);
@@ -280,10 +289,26 @@
         ## fixes the axis limits
         set (ca, "xlimmode", "manual", "ylimmode", "manual",
                  "zlimmode", "manual");
+      elseif (strcmpi (opt, "tickaligned"))
+        ## sets the axis limits to closest ticks.
+        set (ca, "xlimmode", "auto", "ylimmode", "auto", ...
+                 "zlimmode", "auto", ...
+                 "xlimitmethod", "tickaligned", ...
+                 "ylimitmethod", "tickaligned", ...
+                 "zlimitmethod", "tickaligned");
       elseif (strcmpi (opt, "tight"))
         ## sets the axis limits to the min and max of all data.
-        __do_tight_option__ (ca);
-
+        set (ca, "xlimmode", "auto", "ylimmode", "auto", ...
+                 "zlimmode", "auto", ...
+                 "xlimitmethod", "tight", "ylimitmethod", "tight",
+                 "zlimitmethod", "tight");
+      elseif (strcmpi (opt, "padded"))
+        ## sets the axis limits to the min and max of all data plus padding.
+        set (ca, "xlimmode", "auto", "ylimmode", "auto", ...
+                 "zlimmode", "auto", ...
+                 "xlimitmethod", "padded", ...
+                 "ylimitmethod", "padded", ...
+                 "zlimitmethod", "padded");
       ## visibility
       elseif (strcmpi (opt, "on"))
         set (ca, "visible", "on");
@@ -376,87 +401,6 @@
 
 endfunction
 
-## Find the limits for axis ("tight").
-## AX should be one of "x", "y", or "z".
-function lims = __get_tight_lims__ (ca, ax)
-
-  kids = findobj (ca, "-property", [ax "data"]);
-  ## The data properties for hggroups mirror their children.
-  ## Exclude the redundant hggroup values.
-  hg_kids = findobj (kids, "type", "hggroup");
-  kids = setdiff (kids, hg_kids);
-  if (isempty (kids))
-    ## Return the current limits.
-    ## FIXME: Is this the correct thing to do?
-    lims = get (ca, [ax "lim"]);
-  else
-    data = get (kids, [ax "data"]);
-    types = get (kids, "type");
-
-    scale = get (ca, [ax "scale"]);
-    if (! iscell (data))
-      data = {data};
-    endif
-
-    ## Extend image data one pixel
-    idx = strcmp (types, "image");
-    if (any (idx) && (ax == "x" || ax == "y"))
-      imdata = data(idx);
-      px = arrayfun (@__image_pixel_size__, kids(idx), "uniformoutput", false);
-      ipx = ifelse (ax == "x", 1, 2);
-      imdata = cellfun (@(x,dx) [(min (x) - dx(ipx)), (max (x) + dx(ipx))],
-                        imdata, px, "uniformoutput", false);
-      data(idx) = imdata;
-    endif
-
-    if (strcmp (scale, "log"))
-      tmp = data;
-      data = cellfun (@(x) x(x>0), tmp, "uniformoutput", false);
-      n = cellfun ("isempty", data);
-      data(n) = cellfun (@(x) x(x<0), tmp(n), "uniformoutput", false);
-    endif
-    data = cellfun (@(x) x(isfinite (x)), data, "uniformoutput", false);
-    data = data(! cellfun ("isempty", data));
-    if (! isempty (data))
-      ## Change data from cell array of various sizes to a single column vector
-      data = cat (1, cellindexmat (data, ":"){:});
-      lims = [min(data), max(data)];
-    else
-      lims = [0, 1];
-    endif
-  endif
-
-endfunction
-
-function __do_tight_option__ (ca)
-
-  xlim = __get_tight_lims__ (ca, "x");
-  if (all (xlim == 0))
-    xlim = [-eps, +eps];
-  elseif (diff (xlim == 0))
-    xlim .*= [1-eps, 1+eps];
-  endif
-  ylim = __get_tight_lims__ (ca, "y");
-  if (all (ylim == 0))
-    ylim = [-eps, +eps];
-  elseif (diff (ylim == 0))
-    ylim .*= [1-eps, 1+eps];
-  endif
-  set (ca, "xlim", xlim, "ylim", ylim);
-  nd = __calc_dimensions__ (ca);
-  is3dview = (get (ca, "view")(2) != 90);
-  if (nd > 2 && is3dview)
-    zlim = __get_tight_lims__ (ca, "z");
-    if (all (zlim == 0))
-      zlim = [-eps, +eps];
-    elseif (diff (zlim == 0))
-      zlim .*= [1-eps, 1+eps];
-    endif
-    set (ca, "zlim", zlim);
-  endif
-
-endfunction
-
 
 %!demo
 %! clf;
@@ -618,6 +562,38 @@
 %! axis tight;
 %! title ('"tight" on loglog plot');
 
+%!demo
+%! clf;
+%! x = y = 0.5:0.5:12;
+%! subplot (3,1,1);
+%! plot (x, y, "-s");
+%! axis tickaligned
+%! title ("tickaligned");
+%! subplot (3,1,2);
+%! plot (x, y, "-s");
+%! axis padded
+%! title ("padded");
+%! subplot (3,1,3);
+%! plot (x, y, "-s");
+%! axis tight
+%! title ("tight");
+
+%!demo
+%! clf;
+%! x = y = 0.5:0.5:12;
+%! subplot (3,1,1);
+%! loglog (x, y, "-s");
+%! axis tickaligned
+%! title ("tickaligned");
+%! subplot (3,1,2);
+%! loglog (x, y, "-s");
+%! axis padded
+%! title ("padded");
+%! subplot (3,1,3);
+%! loglog (x, y, "-s");
+%! axis tight
+%! title ("tight");
+
 %!test
 %! hf = figure ("visible", "off");
 %! unwind_protect
@@ -655,6 +631,19 @@
 %!   close (hf);
 %! end_unwind_protect
 
+## Test 'axis tight' remains after addition of new data
+%!test
+%! hf = figure ("visible", "off");
+%! unwind_protect
+%!   plot (1:10)
+%!   axis tight;
+%!   assert (axis (), [1 10 1 10]);
+%!   plot (1:11)
+%!   assert (axis (), [1 11 1 11]);
+%! unwind_protect_cleanup
+%!   close (hf);
+%! end_unwind_protect
+
 ## Even on errors, axis can display a figure.
 %!error <LIMITS vector must have .* elements>
 %! hf = figure ("visible", "off");