changeset 24499:941ea3da921f

axes.m: Fix stacking of axes when legends, colorbars, annotations are present (bug #44410). * axes.m: Change documentation to note that a "Cartesian" axes object is created. Expand explanation of how axes objects are re-stacked to note that legends and colorbars are also pulled to the top of the stack. Rename variable "cf" to "hpar" because parent of axes is not always a figure. If new axes is being created there are no colorbars or legends, so handle the single possibility remaining of restacking an annotation layer directly in axes() function. Add complex BIST test for restacking multiple axes with multiple legends, colorbars, and annotation layer. * axes.m (restack_axes): Rename variable "cf" to "hpar". Rename variable "show" to "shh" for clarity. Add code to find any legend or colorbar axes associated with this axes and restack them above the actual axes object so they are visible.
author Rik <rik@octave.org>
date Wed, 03 Jan 2018 10:52:08 -0800
parents 5865d2fef424
children 5553f0ada5c6
files scripts/plot/util/axes.m
diffstat 1 files changed, 140 insertions(+), 64 deletions(-) [+]
line wrap: on
line diff
--- a/scripts/plot/util/axes.m	Wed Jan 03 08:10:48 2018 -0800
+++ b/scripts/plot/util/axes.m	Wed Jan 03 10:52:08 2018 -0800
@@ -21,18 +21,21 @@
 ## @deftypefnx {} {} axes (@var{property}, @var{value}, @dots{})
 ## @deftypefnx {} {} axes (@var{hax})
 ## @deftypefnx {} {@var{h} =} axes (@dots{})
-## Create an axes object and return a handle to it, or set the current axes
-## to @var{hax}.
+## Create a Cartesian axes object and return a handle to it, or set the current
+## axes to @var{hax}.
 ##
 ## Called without any arguments, or with @var{property}/@var{value} pairs,
 ## construct a new axes.  For accepted properties and corresponding values,
 ## @pxref{XREFset,,set}.
 ##
 ## Called with a single axes handle argument @var{hax}, the function makes
-## @var{hax} the current axes.  It also restacks the axes in the corresponding
-## figure so that @var{hax} is the first entry in the list of children.  This
-## causes @var{hax} to be displayed on top of any other axes objects (Z-order
-## stacking).
+## @var{hax} the current axes (as returned by @code{gca}).  It also makes
+## the figure which contains @var{hax} the current figure (as returned by
+## @code{gcf}).  Finally, it restacks the parent object's @code{children}
+## property so that the axes @var{hax} appears before all other axes handles
+## in the list.  This causes @var{hax} to be displayed on top of any other axes
+## objects (Z-order stacking).  In addition it restacks any legend or colorbar
+## objects associated with @var{hax} so that they are also visible.
 ##
 ## @seealso{gca, set, get}
 ## @end deftypefn
@@ -42,51 +45,57 @@
 function h = axes (varargin)
 
   if (nargin == 0 || nargin > 1)
-    ## Parent figure
+    ## Parent handle
     idx = find (strcmpi (varargin(1:2:end), "parent"), 1, "first");
-    if (! isempty (idx) && length (varargin) >= 2*idx)
-      cf = varargin{2*idx};
+    if (! isempty (idx) && numel (varargin) >= 2*idx)
+      hpar = varargin{2*idx};
       varargin([2*idx-1, 2*idx]) = [];
     else
-      cf = gcf ();
+      hpar = gcf ();
     endif
 
-    ## If there is an annotation axes currently on top of the
-    ## figure children stack, then we will put it back on top
-    ## FIXME: Should all annotation axes always be put ahead of regular axes?
-    do_restack = false;
-    ch = allchild (cf);
-    hax = ch(isaxes (ch));
-    if (! isempty (hax) && strcmp (get (hax(1), "tag"), "scribeoverlay"))
-      h_annotation = hax(1);
-      do_restack = true;
+    ## If there is an annotation axes currently on top of the children stack,
+    ## then it must be placed back on top.
+    ## FIXME: It may be necessary to keep uiXXX objects above all axes objects
+    ##        including even the transparent scribe axes layer.
+    ch = allchild (hpar);
+    h_annotation = ch(strcmp (get (ch, "tag"), "scribeoverlay"));
+
+    ## Create an axes object.
+    htmp = __go_axes__ (hpar, varargin{:});
+    if (__is_handle_visible__ (htmp))
+      set (ancestor (hpar, "figure"), "currentaxes", htmp);
     endif
 
-    ## Create an axes object.
-    htmp = __go_axes__ (cf, varargin{:});
-    if (__is_handle_visible__ (htmp))
-      set (ancestor (cf, "figure"), "currentaxes", htmp);
+    ## Restack annotation object if necessary
+    if (! isempty (h_annotation))
+      ## FIXME: This will put annotation layer first, above even uicontrol
+      ## objects.  May need to keep annotation layer above all axes only.
+      shh = get (0, "ShowHiddenHandles");
+      unwind_protect
+        set (0, "ShowHiddenHandles", "on");
+        ch(ch == h_annotation) = htmp;
+        ch = [h_annotation; ch];
+        set (hpar, "children", ch);
+      unwind_protect_cleanup
+        set (0, "ShowHiddenHandles", shh);
+      end_unwind_protect
     endif
 
-    ## Restack if necessary
-    if (do_restack)
-      restack_axes (h_annotation, cf);
-    endif
   else
     ## ARG is axes handle.
     htmp = varargin{1};
-    if (isscalar (htmp) && isaxes (htmp))
-      cf = ancestor (htmp, "figure");
-      if (__is_handle_visible__ (htmp))
-        set (0, "currentfigure", cf);
-        set (cf, "currentaxes", htmp);
-      endif
-
-      ## restack
-      restack_axes (htmp, cf);
-    else
+    if (! isscalar (htmp) || ! isaxes (htmp))
       error ("axes: H must be a scalar axes handle");
     endif
+
+    cf = ancestor (htmp, "figure");
+    if (__is_handle_visible__ (htmp))
+      set (0, "currentfigure", cf);
+      set (cf, "currentaxes", htmp);
+    endif
+
+    restack_axes (htmp, get (htmp, "parent"));
   endif
 
   if (nargout > 0)
@@ -95,30 +104,56 @@
 
 endfunction
 
-function restack_axes (h, cf)
+function restack_axes (h, hpar)
 
-  show = get (0, "showhiddenhandles");
+  shh = get (0, "ShowHiddenHandles");
   unwind_protect
-    set (0, "showhiddenhandles", "on");
-    ch = get (cf, "children");
-    axidx = isaxes (ch);
-    hax = ch(axidx);
-    ## Stack the legend associated with this axes on top of the axes itself
-    hleg = hax(strcmp (get (hax, "tag"), "legend"));
-    if (any (hleg))
-      ## Get axes handles associated with legend
-      if (isscalar (hleg))
-        hlegaxes = getappdata (hleg, "__axes_handle__");
+    set (0, "ShowHiddenHandles", "on");
+    ch = get (hpar, "children");
+    axidx = strcmp (get (ch, "type"), "axes");
+    ## Strip out any annotation axes layer, unless h itself is annotation axes.
+    if (! strcmp (get (h, "tag"), "scribeoverlay"))
+      axidx(axidx) = ! strcmp (get (ch(axidx), "tag"), "scribeoverlay");
+    endif
+    hax = ch(axidx);  # List of axes
+
+    ## Find and stack any legend belonging to this axes above this axes.
+    try
+      hleg = get (h, "__legend_handle__");
+    catch
+      hleg = false;
+    end_try_catch
+
+    ## Find and stack any colorbar belonging to this axes above this axes.
+    try
+      hcb = get (h, "__colorbar_handle__");
+    catch
+      hcb = false;
+    end_try_catch
+
+    ## Preserve order of colorbars and legends above this axes
+    if (hleg || hcb)
+      if (hleg && ! hcb)
+        h = [hleg; h];
+      elseif (hcb && ! hleg)
+        h = [hcb; h];
       else
-        hlegaxes = [getappdata(hleg, "__axes_handle__"){:}](:);
+        hcb_idx = find (hcb == hax);
+        hleg_idx = find (hleg == hax);
+        if (hleg_idx < hcb_idx)
+          h = [hleg; hcb; h];
+        else
+          h = [hcb; hleg; h];
+        endif
       endif
-      hleg = hleg(hlegaxes == h);
-      h = [hleg; h];
     endif
+
+    ## FIXME: ismember call is very slow (2/3rds of runtime for function)
     ch(axidx) = [h; hax(! ismember (hax, h))];
-    set (cf, "children", ch);
+    set (hpar, "children", ch);
+
   unwind_protect_cleanup
-    set (0, "showhiddenhandles", show);
+    set (0, "ShowHiddenHandles", shh);
   end_unwind_protect
 
 endfunction
@@ -188,30 +223,71 @@
 %!test
 %! hf = figure ("visible", "off");
 %! unwind_protect
-%!   hax1 = axes ();
+%!   hax1 = axes ("tag", "axes1");
 %!   plot (1:10, "b");
-%!   hleg1 = legend ("hax1");
-%!   hax2 = axes ();
+%!   hleg1 = legend ("leg1", "location", "east");
+%!   hcb1 = colorbar ("location", "east");
+%!   hanno = annotation ("arrow");
+%!   hscribe = get (hanno, "parent");
+%!
+%!   hax2 = axes ("tag", "axes2");
 %!   plot (10:-1:1, "r");
+%!   hcb2 = colorbar ("location", "east");
 %!   hleg2 = legend ("hax2");
 %!
+%!   ## Verify base configuration
 %!   ch = allchild (hf);
 %!   hax = ch(isaxes (ch));
-%!   assert (find (hax == hax2) < find (hax == hax1));
-%!   assert (find (hax == hleg1) < find (hax == hax1));
-%!   assert (find (hax == hleg2) < find (hax == hax2));
+%!   hax1pos = find (hax1 == hax);
+%!   hax2pos = find (hax2 == hax);
+%!   hleg1pos = find (hleg1 == hax);
+%!   hleg2pos = find (hleg2 == hax);
+%!   hcb1pos = find (hcb1 == hax);
+%!   hcb2pos = find (hcb2 == hax);
+%!   hscribepos = find (hscribe == hax);
 %!
+%!   assert (all (hscribepos < ...
+%!                [hax1pos, hax2pos, hleg1pos, hleg2pos, hcb1pos, hcb2pos]));
+%!   assert (hax2pos < hax1pos);
+%!   assert (hleg2pos < hcb2pos && hcb2pos < hax2pos);
+%!   assert (hcb1pos < hleg1pos && hleg1pos < hax1pos);
+%!
+%!   ## Restack axes1 on top
 %!   axes (hax1);
 %!   ch = allchild (hf);
 %!   hax = ch(isaxes (ch));
-%!   assert (find (hax == hax2) > find (hax == hax1));
-%!   assert (find (hax == hleg1) < find (hax == hax1));
+%!   hax1pos = find (hax1 == hax);
+%!   hax2pos = find (hax2 == hax);
+%!   hleg1pos = find (hleg1 == hax);
+%!   hleg2pos = find (hleg2 == hax);
+%!   hcb1pos = find (hcb1 == hax);
+%!   hcb2pos = find (hcb2 == hax);
+%!   hscribepos = find (hscribe == hax);
 %!
+%!   assert (all (hscribepos < ...
+%!                [hax1pos, hax2pos, hleg1pos, hleg2pos, hcb1pos, hcb2pos]));
+%!   assert (hax1pos < hax2pos);
+%!   assert (hcb1pos < hleg1pos && hleg1pos < hax1pos);
+%!   assert (hleg2pos < hcb2pos && hcb2pos < hax2pos);
+%!
+%!   ## Restack axes2 on top
 %!   axes (hax2);
 %!   ch = allchild (hf);
 %!   hax = ch(isaxes (ch));
-%!   assert (find (hax == hax2) < find (hax == hax1));
-%!   assert (find (hax == hleg2) < find (hax == hax2));
+%!   hax1pos = find (hax1 == hax);
+%!   hax2pos = find (hax2 == hax);
+%!   hleg1pos = find (hleg1 == hax);
+%!   hleg2pos = find (hleg2 == hax);
+%!   hcb1pos = find (hcb1 == hax);
+%!   hcb2pos = find (hcb2 == hax);
+%!   hscribepos = find (hscribe == hax);
+%!
+%!   assert (all (hscribepos < ...
+%!                [hax1pos, hax2pos, hleg1pos, hleg2pos, hcb1pos, hcb2pos]));
+%!   assert (hax2pos < hax1pos);
+%!   assert (hleg2pos < hcb2pos && hcb2pos < hax2pos);
+%!   assert (hcb1pos < hleg1pos && hleg1pos < hax1pos);
+%!
 %! unwind_protect_cleanup
 %!   close (hf);
 %! end_unwind_protect