changeset 19775:b8e4104a8f55

Add the annotation function (bug #43282) * NEWS: add annotation * plot.txi: add annotation in same chapter as x/y/zlabel, title... * __unimplemented__: remove annotation from the list * scripts/plot/draw/annotation.m: new function file * axes.m: restack figure children so that the annotation axes is always drawn on top * axes.m: replace "parent" variable by "cf" for concistency * clf.m: make "clf" delete the hidden annotation axes for matlab compatibility * clf.m: avoid flashing a figure in the last test.
author Pantxo Diribarne <pantxo.diribarne@gmail.com>
date Sun, 08 Feb 2015 22:22:28 +0100
parents ca6fc882177d
children 55a4173f1624
files NEWS doc/interpreter/plot.txi scripts/help/__unimplemented__.m scripts/plot/draw/annotation.m scripts/plot/draw/module.mk scripts/plot/util/axes.m scripts/plot/util/clf.m
diffstat 7 files changed, 1276 insertions(+), 6 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Tue Feb 17 15:24:05 2015 -0800
+++ b/NEWS	Sun Feb 08 22:22:28 2015 +0100
@@ -156,6 +156,7 @@
 
  ** Other new functions added in 4.0:
 
+      annotation
       bandwidth
       cubehelix
       dir_in_loadpath
--- a/doc/interpreter/plot.txi	Tue Feb 17 15:24:05 2015 -0800
+++ b/doc/interpreter/plot.txi	Sun Feb 08 22:22:28 2015 +0100
@@ -468,6 +468,8 @@
 and border lines to the plot.  By default, the grid is off and the
 border lines are on.
 
+Finally, arrows, text and rectangular or elliptic boxes can be added to highlight parts of a plot using the @code{annotation} function. Those objects are drawn in an invisible axes, on top of every other axes.
+
 @DOCSTRING(title)
 
 @DOCSTRING(legend)
@@ -488,6 +490,8 @@
 
 @DOCSTRING(colorbar)
 
+@DOCSTRING(annotation)
+
 @node Multiple Plots on One Page
 @subsection Multiple Plots on One Page
 @cindex plotting, multiple plots per figure
--- a/scripts/help/__unimplemented__.m	Tue Feb 17 15:24:05 2015 -0800
+++ b/scripts/help/__unimplemented__.m	Sun Feb 08 22:22:28 2015 +0100
@@ -540,7 +540,6 @@
   "alim",
   "alpha",
   "alphamap",
-  "annotation",
   "array2table",
   "bar3",
   "bar3h",
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/plot/draw/annotation.m	Sun Feb 08 22:22:28 2015 +0100
@@ -0,0 +1,1239 @@
+## Copyright (C) 2015 Pantxo Diribarne
+## 
+##   This program is free software; you can redistribute it and/or modify
+##   it under the terms of the GNU General Public License as published by
+##   the Free Software Foundation; either version 3 of the License, or
+##   (at your option) any later version.
+##   
+##   This program is distributed in the hope that it will be useful,
+##   but WITHOUT ANY WARRANTY; without even the implied warranty of
+##   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+##   GNU General Public License for more details.
+##   
+##   You should have received a copy of the GNU General Public License
+##   along with Octave; see the file COPYING.  If not, see
+##   <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {Function File} {} annotation (@var{type})
+## @deftypefnx {Function File} {} annotation ("line", @var{x}, @var{y})
+## @deftypefnx {Function File} {} annotation ("arrow", @var{x}, @var{y})
+## @deftypefnx {Function File} {} annotation ("doublearrow", @var{x}, @var{y})
+## @deftypefnx {Function File} {} annotation ("textarrow", @var{x}, @var{y})
+## @deftypefnx {Function File} {} annotation ("textbox", @var{pos})
+## @deftypefnx {Function File} {} annotation ("rectangle", @var{pos})
+## @deftypefnx {Function File} {} annotation ("ellipse", @var{pos})
+## @deftypefnx {Function File} {} annotation (@var{hf}, @dots{})
+## @deftypefnx {Function File} {} annotation (@dots{}, @var{prop}, @var{val})
+## @deftypefnx {Function File} {@var{h} =} annotation (@dots{})
+## Draw annotations to emphasize parts of a figure.
+##
+## You may build a default annotation specifying only the type
+## @var{type} of the annotation. Otherwise you can set the position of
+## the annotation using either @var{x} and @var{y} coordinates for
+## line-based annotations or a position vector @var{pos} for others.
+## In any case, coordinates are interpreted using the @qcode{"units"}
+## property of the annotation objects: the default is
+## @qcode{"normalized"}, which means the lower left hand corner of the
+## figure has coordinates @samp{[0 0]} and the upper right hand corner
+## @samp{[1 1]}. 
+##
+## The figure on which the annotations should be drawn may be 
+## specified by providing its graphics handle @var{hf} before any
+## other argument. Otherwise annotations are drawn on the current
+## figure. 
+## 
+## Further arguments can be provided in the form of
+## @var{prop}/@var{val} pairs to customize the annotation  appearance
+## and the units in which coordinates are interpreted. The annotation
+## can also be customized afterward using its graphics handle 
+## @var{h} and @code{set} function.
+## 
+## All annotation objects share two properties:
+## @itemize
+## @item
+##  @qcode{"units"}: the units in which coordinates are interpreted.
+##  Its value may be one of @qcode{"centimeters"} |
+##  @qcode{"characters"} | @qcode{"inches"} | @qcode{"@{normalized@}"}
+##  | @qcode{"pixels"} | @qcode{"points"}.
+##
+## @item
+##  @qcode{"position"}: a four elements vector [x0 y0 width height]
+##  specifying  the coordinates (x0,y0) of the origin of the annotation
+##  object, its width and its height. The width and height may be
+##  negative, depending on the orientation of the object.
+##   
+## @end itemize
+##
+## Valid annotation types and their specific properties are described
+## bellow: 
+## @table @asis
+## @item @qcode{"line"}
+##  Constructs a line. @var{x} and @var{y} must be two
+##  elements vectors specifying the x and y coordinates of the two
+##  ends of the line.
+##
+##  The line can be customized using @qcode{"linewidth"},
+##  @qcode{"linestyle"} and @qcode{"color"} properties the same way
+##  as with @code{line} objects.   
+##
+## @item  @qcode{"arrow"}
+##   Construct an arrow. The second point in vectors @var{x} and
+##   @var{y} specifies the arrowhead coordinates.
+##
+##  Besides line properties, the arrowhead can be customized using
+##  @qcode{"headlength"}, @qcode{"headwidth"} and @qcode{"headstyle"}
+##  properties. Supported values for @qcode{"headstyle"} property are:
+##  [@qcode{"diamond"} | @qcode{"ellipse"} | @qcode{"plain"} |
+##  @qcode{"rectangle"} | @qcode{"vback1"} | @qcode{"@{vback2@}"} | 
+##  @qcode{"vback3"}] 
+## 
+## @item  @qcode{"doublearrow"}
+##   Construct a double arrow. Vectors @var{x} and @var{y} specify the
+##   arrowheads coordinates.
+##
+##  The line and the arrowhead can be customized as for arrow
+##  annotations but some property names are duplicated:
+##  @qcode{"head1length"}/@qcode{"head2length"},
+##  @qcode{"head1width"}/@qcode{"head2width"}, etc. The index 1 marks
+##  the properties of the arrowhead at the first point in @var{x} and
+##  @var{y} coordinates. 
+##
+## @item  @qcode{"textarrow"}
+##  Construct an arrow with a text label at the opposite of the
+##  arrowhead.
+##
+##  The line and the arrowhead can be customized as for arrow
+##  annotations, and the text can be customized using the same
+##  properties as @code{text} graphics objects. Note however
+##  that some text property names are prefixed with "text" to
+##  distinguish arrow and text properties:
+##  @qcode{"textbackgroundcolor"}, @qcode{"textcolor"},
+##  @qcode{"textedgecolor"}, @qcode{"textlinewidth"},
+##  @qcode{"textmargin"}, @qcode{"textrotation"}.
+##   
+## @item  @qcode{"textbox"}
+##  Construct a box with a text inside. @var{pos} specifies the
+##  @qcode{"position"} property of the annotation.
+##
+##  You may use @qcode{"backgroundcolor"}, @qcode{"edgecolor"},
+##  @qcode{"linestyle"} , @qcode{"linewidth"} properties to customize
+##  the box background color and edges appearance. A limited set of
+##  @code{text} objects properties is also available: besides
+##  @qcode{"font..."} properties, you may also use
+##  @qcode{"horizontalalignment"} and @qcode{"verticalalignment"} to
+##  move the text inside the box.
+##
+##  Finally the  @qcode{"fitboxtotext"} property controls the actual
+##  extent of the box. If @qcode{"on"} (the default) the
+##  box limits are fitted to the text extent.
+##
+## @item  @qcode{"rectangle"}
+##  Construct a rectangle. @var{pos} specifies the
+##  @qcode{"position"} property of the annotation. 
+##
+##  You may use @qcode{"facecolor"}, @qcode{"color"},
+##  @qcode{"linestyle"} and @qcode{"linewidth"} properties to customize
+##  the rectangle background color and edges appearance.
+##
+## @item  @qcode{"ellipse"}
+##  Construct an ellipse. @var{pos} specifies the
+##  @qcode{"position"} property of the annotation. 
+##
+##  See @qcode{"rectangle"} annotations for customization. 
+## @end table
+## 
+## @seealso{xlabel, title}
+## @end deftypefn
+
+function varargout = annotation (varargin)
+
+  objtype = "";
+  hf = [];
+  lims = [];
+  x = y = [];
+  opts = {};
+  
+  nargin = numel (varargin);
+  if (nargin == 0)
+    print_usage ();
+  endif
+  
+  
+  ## Parent figure
+  if (isfigure (varargin{1}))
+    hf = varargin{1};
+    varargin = varargin(2:end);
+    nargin --;
+  endif
+  
+  ## Annotation type
+  types = {"line", "arrow", "doublearrow", "textarrow", ...
+           "textbox", "ellipse", "rectangle"};
+  if (ischar (varargin{1}))
+    objtype = varargin{1};
+    varargin(1) = [];
+    nargin --;
+  else
+    print_usage ();
+  endif
+  
+  switch objtype
+    case types(1:4)
+         
+      if (nargin == 0)
+        lims  = [];
+      elseif (nargin >= 2)
+        x = varargin{1};
+        y = varargin{2};
+        varargin(1:2) = [];
+      
+        if (isnumeric (x) && isnumeric (y) && 
+            length (x) == 2 && length (y) == 2)
+          lims = [x(1) y(1) diff(x) diff(y)];
+        else 
+          error ("annotation: expect 2 elements vectors for X and Y");
+        endif
+      else
+        print_usage ();
+      endif
+    case types(5:end)
+      if (nargin == 0)
+        lims  = [];
+      else
+        lims = varargin{1};
+        varargin(1) = [];
+        
+        if (! isvector (lims) || length (lims) != 4)
+          error ("annotation: expect 4 elements vector for POS")
+        endif
+      endif
+    otherwise
+      error ("annotation: unknown annotation type %s", objtype)
+  endswitch
+
+  ## options
+  opts = varargin;
+  nopts = numel (opts);
+  if (! isempty (opts))
+    if (fix (nopts/2) != nopts/2 ||
+        !all (cellfun (@ischar, opts(1:2:end))))
+      warning ("annotation: couldn't parse PROP/VAL pairs, skipping");
+      opts = {};
+    endif
+  endif
+  
+  ## create annotation
+  showhidden = get (0, "showhiddenhandles");
+  set (0, "showhiddenhandles", "on");
+
+  unwind_protect
+    if (isempty (hf))
+      hf = gcf ();
+    endif
+    
+    ## Axes
+    hca = get (hf, "currentaxes");
+
+    hax = findall (hf, "-depth", 1, "tag", "scribeoverlay");
+    if (isempty (hax))
+      hax = buildoverlay (hf);
+    endif
+
+    ## Build annotation
+    htmp = buildannot (hax, objtype, lims);
+
+    ## Set user defined properties
+    if (!isempty (opts))
+      set (htmp, opts{:});
+    endif
+    
+  unwind_protect_cleanup
+    set (0, "showhiddenhandles", showhidden);
+    set (hf, "currentaxes", hca);
+  end_unwind_protect
+  
+  if (nargout != 0)
+    varargout{1} = htmp;
+  endif
+
+endfunction
+
+function hax = buildoverlay (hf)
+
+  hax = axes ("parent", hf, "position", [0 0 1 1], ...
+              "visible", "off","tag", "scribeoverlay", ...
+              "xlim", [0 1], "ylim", [0 1], ...
+              "handlevisibility", "off");
+  
+  ## hidden property to store figure size in absolute (points)
+  ## coordinates 
+  addproperty ("figsize_points", hax, "axesxmtick", []);
+  update_figsize_points (hf, {}, hax);
+
+  
+  listener = {@update_figsize_points, hax};
+  addlistener (hf, "position", listener);
+    
+  delfcn = @() dellistener (hf, "position", listener);
+  set (hax, "deletefcn", delfcn);
+  
+endfunction
+
+function update_figsize_points (hf, dummy, hax)
+
+  persistent recursive = false;
+  if (! recursive)
+    recursive = true;
+    units = get (hf, "units");
+    set (hf, "units", "points");
+    pos = get (hf, "position");
+    set (hf, "units", units);
+    
+    set (hax, "figsize_points", pos(3:4));
+    recursive = false;
+  endif
+  
+endfunction
+  
+function h = buildannot (hax, objtype, pos)
+
+  ## Base hggroup
+  h = hggroup ("parent", hax);
+  
+  ## Add common properties
+  if (strcmp (graphics_toolkit (), "gnuplot"))
+    ## FIXME: this is a workaround for bug #39394 (gnuplot toolkit)
+    defprops = {"position", "axesposition", [0.3 0.3 0.1 0.1], ...
+                "units", "textunits", "data"}; 
+  else
+    defprops = {"position", "axesposition", [0.3 0.3 0.1 0.1], ...
+                "units", "axesunits", "normalized"};
+  endif
+  addbaseprops (h, defprops);
+  setappdata (h, "__former_units__", "normalized");
+
+  ## Common updaters
+  listener = {@update_position, h, true};
+  
+  addlistener (hax, "figsize_points", listener);
+  
+  delfcn = @() dellistener (hax, "figsize_points", listener);
+  set (h, "deletefcn", delfcn);
+  
+  addlistener (h, "units", {@update_position, h});
+  
+  ## Now work with normalized coordinates
+  if (! isempty (pos))
+    set (h, "position", pos);
+  endif
+  pos = getnormpos (h);
+  
+  ## Build annotation object and its specific properties/updaters
+  switch objtype
+    case {"line", "arrow", "doublearrow", "textarrow"}
+      ## Add properties
+      proptable = lineprops ();
+      if (strcmp (objtype, "arrow"))
+        proptable = [proptable arrowprops()];
+      elseif (strcmp (objtype, "doublearrow"))
+        proptable = [proptable dblarrowprops()];
+      elseif (strcmp (objtype, "textarrow"))
+        proptable = [proptable arrowprops()];
+        proptable = [proptable textprops()];
+      endif
+
+      addbaseprops (h, proptable);
+      
+      ## create line
+      hli = line ([pos(1); (pos(1) + pos(3))],
+                  [pos(2); (pos(2) + pos(4))],
+                  "parent", h, "color", get (h, "color"),
+                  "linestyle", get (h, "linestyle"),
+                  "linewidth", get (h, "linewidth"));
+      
+      ## create patch(s) and text
+      if (strcmp (objtype, "arrow"))
+        [x, y] = arrowcoordinates (h);
+        hpa = patch (x, y, get (h, "color"), "parent", h,
+                    "edgecolor",  get (h, "color"));
+        update_arrow (h, {}, "position", hpa);
+      elseif (strcmp (objtype, "doublearrow"))
+        [x, y] = arrowcoordinates (h, 1);
+        hpa = patch (x, y, get (h, "color"), "parent", h,
+                    "edgecolor",  get (h, "color"));
+        
+        [x, y] = arrowcoordinates (h, 2);
+        hpa(2) = patch (x, y, get (h, "color"), "parent", h,
+                    "edgecolor",  get (h, "color"));
+        
+        update_arrow (h, {}, "position", hpa);
+      elseif (strcmp (objtype, "textarrow"))
+        [x, y] = arrowcoordinates (h);
+        hpa = patch (x, y, get (h, "color"), "parent", h,
+                    "edgecolor",  get (h, "color"));
+        update_arrow (h, {}, "position", hpa);
+        
+        hte = text (get (h, "position")(1), ...
+                   get (h, "position")(2), ...
+                   get (h, "string"), "parent", h, ...
+                   "color", get (h, "color"));
+        propnames = textprops ("names");
+        for ii = 1:numel (propnames)
+          update_text (h, {}, propnames{ii}, hte);
+        endfor
+        update_text (h, {}, "position", hte);
+      endif
+      
+      ## updaters
+      addlistener (h, "color", {@update_line, "color", hli});
+      addlistener (h, "linestyle", {@update_line, "linestyle", hli});
+      addlistener (h, "linewidth", {@update_line, "linewidth", hli});
+      addlistener (h, "x", {@update_line, "x", hli});
+      addlistener (h, "y", {@update_line, "y", hli});
+      addlistener (h, "position", {@update_line, "position", hli});
+
+      if (strcmp (objtype, "arrow"))
+        addlistener (h, "position", {@update_arrow, "position", hpa});
+        addlistener (h, "headwidth", {@update_arrow, "position", hpa});
+        addlistener (h, "headstyle", {@update_arrow, "position", hpa});
+        addlistener (h, "headlength", {@update_arrow, "position", hpa});
+        addlistener (h, "color", {@update_arrow, "color", hpa});
+      elseif (strcmp (objtype, "doublearrow"))
+        addlistener (h, "position", {@update_arrow, "position", hpa});
+        addlistener (h, "head1width", 
+                     {@update_arrow, "position", [hpa(1) 0]});
+        addlistener (h, "head2width",
+                     {@update_arrow, "position", [0 hpa(2)]});
+        addlistener (h, "head1style",
+                     {@update_arrow, "position", [hpa(1) 0]});
+        addlistener (h, "head2style",
+                     {@update_arrow, "position", [0 hpa(2)]});
+        addlistener (h, "head1length",
+                     {@update_arrow, "position", [hpa(1) 0]});
+        addlistener (h, "head2length",
+                     {@update_arrow, "position", [0 hpa(2)]});
+        addlistener (h, "color",
+                     {@update_arrow, "color", hpa});
+      elseif (strcmp (objtype, "textarrow"))
+        addlistener (h, "position", {@update_arrow, "position", hpa});
+        addlistener (h, "headwidth", {@update_arrow, "position", hpa});
+        addlistener (h, "headstyle", {@update_arrow, "position", hpa});
+        addlistener (h, "headlength", {@update_arrow, "position", hpa});
+        addlistener (h, "color", {@update_arrow, "color", hpa});
+        propnames = textprops ("names");
+        for ii = 1:numel (propnames)
+          addlistener (h, propnames{ii},
+                       {@update_text, propnames{ii}, hte});
+          if (any (strcmp (propnames{ii},
+                           {"fontangle", "fontname", ...
+                            "fontsize", "fontweight", ...
+                            "horizontalalignment", "string", ...
+                            "textmargin", "textrotation", ...
+                            "verticalalignment"})))
+            addlistener (h, propnames{ii}, ...
+                         {@update_text, "position", hte});
+          endif
+        endfor
+        addlistener (h, "position", {@update_text, "position", hte});
+        addlistener (h, "color", {@update_text, "color", hte});
+      endif
+      
+    case {"rectangle", "ellipse"}
+         
+      ## Add properties
+      addbaseprops (h, rectprops ());
+      
+      ## Create rectangle/ellipse
+      if (strcmp (objtype, "rectangle"))
+        [x, y] = pos2rect (pos);
+      else
+        [x, y] = pos2ell (pos);
+      endif
+          
+      hr =  patch (x, y, "parent", h);
+      
+      propnames = rectprops ("names");
+      for ii = 1:numel (propnames)
+        update_rect (h, {}, propnames{ii}, hr, objtype);
+      endfor
+
+      ## Updaters
+      addlistener (h, "position", {@update_rect, "position", hr, objtype});
+      for ii = 1:numel (propnames)
+        addlistener (h, propnames{ii},
+                     {@update_rect, propnames{ii}, hr, objtype});
+      endfor
+
+    case "textbox"
+         
+      ## Add properties
+      addbaseprops (h, textboxprops());
+
+      ## Create textbox
+      hpa = patch ("parent", h);
+      hte = text (pos(1), pos(2), get (h, "string"), "parent", h, ...
+                 "color", get (h, "color"));
+      update_textbox (h, {}, "position", [hte hpa]);
+      
+      propnames = textboxprops ("names");
+      for ii = 1:numel (propnames)
+        update_textbox (h, {}, propnames{ii}, [hte hpa]);
+      endfor
+
+      ## Updaters
+      addlistener (h, "position", {@update_textbox, "position", [hte hpa]});
+      for ii = 1:numel (propnames)
+        addlistener (h, propnames{ii},
+                     {@update_textbox, propnames{ii}, [hte hpa]});
+      endfor
+      addlistener (h, "horizontalalignment",
+                   {@update_textbox, "position", [hte hpa]});
+      addlistener (h, "verticalalignment",
+                   {@update_textbox, "position", [hte hpa]});
+      
+  endswitch
+  
+endfunction
+
+function props = lineprops (varargin)
+  props = {"color", "color", [0 0 0], ...
+           "linestyle",  "linelinestyle", "-", ...
+           "linewidth", "linelinewidth", 0.5, ...
+           "x", "linexdata", [0.3 0.4], ...
+           "y", "lineydata", [0.3 0.4]};
+  if (strcmp (varargin, "names"))
+    props = props(1:3:end);
+  endif
+endfunction
+
+function props = arrowprops (varargin)
+  props = {"headlength", "data", 10, ...
+           "headstyle",  "radio", "diamond|ellipse|none|plain|rectangle|vback1|{vback2}|vback3", ...
+           "headwidth", "data", 10};
+  if (strcmp (varargin, "names"))
+    props = props(1:3:end);
+  endif
+endfunction
+
+function props = dblarrowprops (varargin)
+  props = {"head1length", "data", 10, ...
+           "head1style",  "radio", "diamond|ellipse|none|plain|rectangle|vback1|{vback2}|vback3", ...
+           "head1width", "data", 10, ...
+           "head2length", "data", 10, ...
+           "head2style",  "radio", "diamond|ellipse|none|plain|rectangle|vback1|{vback2}|vback3", ...
+           "head2width", "data", 10};
+  if (strcmp (varargin, "names"))
+    props = props(1:3:end);
+  endif
+endfunction
+
+function props = textprops (varargin)
+  props = {"fontangle", "textfontangle", "normal", ...
+           "fontname",  "textfontname", "Helvetica", ...
+           "fontsize", "textfontsize", 10, ...
+           "fontunits", "textfontunits", "points", ...
+           "fontweight",  "textfontweight", "normal", ...
+           "horizontalalignment", "texthorizontalalignment", "left", ...
+           "interpreter", "textinterpreter", "tex", ...
+           "string", "textstring", "", ...
+           "textbackgroundcolor", "textbackgroundcolor", "none", ...
+           "textcolor", "textcolor", "k", ...
+           "textedgecolor", "textedgecolor", "none", ...
+           "textlinewidth", "textlinewidth",0.5, ...
+           "textmargin", "textmargin", 5, ...
+           "textrotation", "textrotation", 0, ...
+           "verticalalignment", "textverticalalignment", "middle"};
+  if (strcmp (varargin, "names"))
+    props = props(1:3:end);
+  endif
+endfunction
+
+function props = textboxprops (varargin)
+  props = {"backgroundcolor", "patchfacecolor", "none", ...
+           "color", "textcolor", [0 0 0], ...
+           "edgecolor", "patchedgecolor", [0 0 0], ...
+           "facealpha", "patchfacealpha", 1, ...
+           "fontangle", "textfontangle", "normal", ...
+           "fontname",  "textfontname", "Helvetica", ...
+           "fontsize", "textfontsize", 10, ...
+           "fontunits", "textfontunits", "points", ...
+           "fontweight",  "textfontweight", "normal", ...
+           "horizontalalignment", "texthorizontalalignment", "left", ...
+           "interpreter", "textinterpreter", "tex", ...
+           "linestyle",  "linelinestyle", "-", ...
+           "linewidth", "linelinewidth", 0.5, ...
+           "string", "textstring", "", ...
+           "fitboxtotext", "radio","{on}|off", ...
+           "margin", "data", 5, ...
+           "verticalalignment", "textverticalalignment",  "middle"};
+  if (strcmp (varargin, "names"))
+    props = props(1:3:end);
+  endif
+endfunction
+
+function props = rectprops (varargin)
+  props = {"edgecolor", "patchedgecolor", "k", ...
+           "facealpha", "patchfacealpha", 1, ...
+           "facecolor", "patchfacecolor", "none", ...
+           "linestyle", "patchlinestyle", "-", ...
+           "linewidth", "patchlinewidth", 0.5};
+  if (strcmp (varargin, "names"))
+    props = props(1:3:end);
+  endif
+endfunction
+
+function addbaseprops (h, proptable)
+  cellfun (@(pname, ptype, parg) addproperty (pname, h, ptype, parg),
+           proptable(1:3:end), proptable(2:3:end), proptable(3:3:end));
+endfunction
+
+function update_position (h1, dummy, h, force = false)
+  if (! force)
+    pos = convertposition (h, getappdata (h, "__former_units__"),
+                           get (h, "units"));
+    setappdata (h, "__former_units__", get (h, "units"));
+    set (h, "position", pos);
+  else
+    ## FIXME: Inefficient trick to force all objects to be redrawn
+    set (h, "position", [0 0 .5 .5],
+         "position", get (h, "position"));
+  endif      
+endfunction
+
+
+function update_line (h, dummy, prop, hli)
+  persistent recursive = false;
+  if (! recursive)
+    switch prop
+      case "color"
+        set (hli, "color", get (h, "color"));
+      case "linestyle"
+        set (hli, "linestyle", get (h, "linestyle"));
+      case "linewidth"
+        set (hli, "linewidth", get (h, "linewidth"));
+      case "x"
+        ## Update position
+        x = get (h, "x");
+        pos = get (h, "position");
+        pos(1) = x(1);
+        pos(3) = diff (x);
+        recursive = true;
+        set (h, "position", pos);
+        recursive = false;
+
+        ## Draw in normalized coordinates
+        pos = getnormpos (h);
+        x = [pos(1) (pos(1) + pos(3))];
+        set (hli, "xdata", x);
+        
+      case "y"
+       ## Update position
+        y = get (h, "y");
+        pos = get (h, "position");
+        pos(2) = y(1);
+        pos(4) = diff (y);
+        recursive = true;
+        set (h, "position", pos);
+        recursive = false;
+
+        ## Draw in normalized coordinates
+        pos = getnormpos (h);
+        y = [pos(2) (pos(2) + pos(4))];
+        set (hli, "ydata", y);
+      case "position"
+        ## Update x and y
+        pos = get (h, "position");
+        x = [pos(1) (pos(1) + pos(3))];
+        y = [pos(2) (pos(2) + pos(4))];
+        
+        recursive = true;
+        set (h, "x", x);
+        set (h, "y", y);
+        recursive = false;
+        
+        ## Draw in normalized coordinates
+        pos = getnormpos (h);
+        x = [pos(1) (pos(1) + pos(3))];
+        y = [pos(2) (pos(2) + pos(4))];
+        set (hli, "xdata", x);
+        set (hli, "ydata", y);
+        
+    endswitch
+  endif
+endfunction
+
+function [x, y] = arrowcoordinates (h, nar = [])
+  warning ("off", "Octave:broadcast", "local")
+  pos = getnormpos (h);
+  ppos = norm2pts (h, pos(3:4).');
+  ang = angle (complex (ppos(1), ppos(2)));
+
+  if (isempty (nar))
+    ln = get (h, "headlength");   # in points
+    wd = get (h, "headwidth");
+    headstyle = get (h, "headstyle");
+    pos = pos(1:2) .+ pos(3:4);
+  elseif (nar == 1)
+    ln = get (h, "head1length"); # in points
+    wd = get (h, "head1width");
+    headstyle = get (h, "head1style");
+    pos = pos(1:2);
+    ang += pi;
+  elseif (nar == 2)
+    ln = get (h, "head2length"); # in points
+    wd = get (h, "head2width");
+    headstyle = get (h, "head2style");
+    pos = pos(1:2) .+ pos(3:4);
+  else
+    error ("annotation: %d, no such arrow number")
+  endif
+
+  switch headstyle
+    case "diamond"
+      x = [0 -ln/2 -ln -ln/2 0];
+      y = [0 -wd/2 0 wd/2 0];
+    case "ellipse"
+      pts = linspace (0, 2*pi, 12);
+      x = ln/2 * (cos (pts) - 1);
+      y = wd/2 * sin (pts);
+    case "rectangle"
+      x = [0 0 -ln -ln 0];
+      y = [wd/2 -wd/2 -wd/2 wd/2 wd/2];
+    case "vback1"
+      x = [0 -ln -0.85*ln -ln 0];
+      y = [0 wd/2 0 -wd/2 0];
+    case "vback2"
+      x = [0 -ln -0.65*ln -ln 0];
+      y = [0 wd/2 0 -wd/2 0];
+    case "vback3"
+      x = [0 -ln -0.2*ln -ln 0];
+      y = [0 wd/2 0 -wd/2 0];
+    case "plain"
+      x = [0 -ln -ln -ln 0];
+      y = [0 wd/2 0 -wd/2 0];
+    case "none"
+      x = [0 0 0];
+      y = [0 0 0];
+    otherwise
+      error ("annotation: \"%s\" headstyle not implemented", headstyle)
+  endswitch
+
+  R = [cos(ang) -sin(ang);
+       sin(ang) cos(ang)];
+  XY = R * [x; y];
+  XY = pts2norm (h, XY);
+  XY = pos(1:2).' .+ XY;
+  
+  x = XY(1,:).';
+  y = XY(2,:).';
+endfunction
+
+function update_arrow (h, dummy, prop, hpa = [])
+  persistent recursive = false;
+  
+  nar = [];
+  for ii = 1:numel (hpa)
+    if (numel (hpa) == 2)
+      nar = ii;
+    endif
+    if (hpa(ii))
+      switch prop
+        case "position"
+          [x, y] = arrowcoordinates (h, nar);
+          set (hpa(ii), "xdata", x, "ydata", y);
+        case "color"
+          set (hpa(ii), "facecolor", get (h, "color"));
+          set (hpa(ii), "edgecolor", get (h, "color"));
+      endswitch
+    endif
+  endfor
+endfunction
+
+function update_text (h, dummy, prop, hte)
+  persistent recursive = false;
+
+  if (! recursive)
+    switch prop
+      case "position"
+        if (isempty (get (h, "string")))
+          return
+        endif
+        
+        pos = getnormpos (h);
+        
+        set (hte, "position", [textcoordinates(hte, pos) 0]);
+        
+      otherwise
+        if (strncmp (prop, "text", 4))
+          set (hte, prop(5:end), get (h, prop));
+        else
+          set (hte, prop, get (h, prop));
+        endif
+    endswitch
+  endif
+endfunction
+
+function update_textbox (h, dummy, prop, htb)
+  persistent recursive = false;
+
+  hte = htb(1);
+  hpa = htb(2);
+  
+  if (! recursive)
+    switch prop
+      case {"edgecolor", "facealpha",
+            "linestyle", "linewidth"}
+        set (hpa, prop, get (h, prop));
+      case {"backgroundcolor"}
+        set (hpa, "facecolor", get (h, prop));
+      otherwise
+        if (! any (strcmp (prop, {"fitboxtotext", "position"})))
+          set (hte, prop, get (h, prop));
+        endif
+           
+        pos = getnormpos (h);
+        
+        if (strcmp (get (h, "fitboxtotext"), "on"))
+          pos(3:4) = get (hte, "extent")(3:4);
+        endif
+        
+        [x, y] = pos2rect (pos);
+        set (hpa, "xdata", x', "ydata", y');
+          
+        switch (get (h, "horizontalalignment"))
+          case "left"
+            x = x(1);
+          case "center"
+            x = mean (x(1:2));
+          case "right"
+            x = x(2);
+        endswitch
+
+        switch (get (h, "verticalalignment"))
+          case {"top", "cap"}
+            y = y(3);
+          case "middle"
+            y = mean (y(2:3));
+          case {"bottom", "baseline"}
+            y = y(2);
+        endswitch
+        set (hte, "position", [x y 0]);
+    endswitch
+  endif
+  
+endfunction
+
+function XY = textcoordinates (hte, pos)
+  warning ("off", "Octave:broadcast", "local")
+
+  ## Get the "tight" extent of the text object in points units
+  textpos = get(hte, "position");
+  rot = get (hte, "rotation");
+  units = get (hte, "units");
+
+  set (hte, "rotation", 0, "units", "points", "position", [0 0 0]);
+  ext = get (hte, "extent");
+  set (hte, "rotation", rot, "units", units, "position", textpos);
+  
+  ## Find which one of the 8 following points we should align the
+  ## arrow with
+  ##  8-----7-----6
+  ##  1  text box 5
+  ##  2-----3-----4
+  
+  ## FIXME: Matlab's horizontal/verticalalignment properties are
+  ## interpreted differently: horizontalalignment is passed to the 
+  ## underlying text object whereas the verticalalignement controls
+  ## the vertical alignment of the arrow.
+  
+  ang = angle (complex (pos(3), pos(4)));
+  rot = rot / 180 * pi;
+
+  [~, pt] = min (abs ((-pi:pi/4:pi) - ang)); 
+  pt -= floor (rot / (pi/4));
+  if (pt <= 0)
+    pt = rem (pt, 8) + 8;
+  elseif (pt > 8)
+    pt = rem (pt, 8);
+  endif
+  
+  ## Compute the text actual "position" property
+  dx = ext(3)/2;
+  dy = ext(4)/2;
+  XY = [-dx -dx 0 dx dx dx 0 -dx;
+        0 -dy -dy -dy 0 dy dy dy];
+  
+  switch get (hte, "horizontalalignment")
+    case "left"
+      XY(1,:) += dx;
+    case "right"
+      XY(1,:) -= dx;
+  endswitch
+  
+  switch get (hte, "verticalalignment")
+    case {"baseline", "bottom"}
+      XY(2,:) += dy;
+    case {"cap", "top"}
+      XY(2,:) -= dy;
+  endswitch
+  
+  R = [cos(rot) -sin(rot);
+       sin(rot) cos(rot)];
+  XY = R * XY;
+  XY = pts2norm (get (hte, "parent"), XY);
+  XY = pos(1:2) .- XY(:,pt).';
+  
+endfunction
+
+function nXY = pts2norm (h, pXY)
+  sz = get (get (h,"parent"), "figsize_points");
+  
+  nXY(1,:) = pXY(1,:) ./ sz(1);
+  nXY(2,:) = pXY(2,:) ./ sz(2);
+endfunction
+
+function pXY = norm2pts (h, nXY)
+  sz = get (get (h,"parent"), "figsize_points");
+  
+  pXY(1,:) = nXY(1,:) .* sz(1);
+  pXY(2,:) = nXY(2,:) .* sz(2);
+endfunction
+
+function pos = convertposition (h, from, to)
+  ## FIXME: handle "characters" units  
+  pos = get (h, "position");
+  
+  ## First convert to normalized coordinates
+  sz = get (get (h,"parent"), "figsize_points");
+  switch from
+    case "centimeters"
+      pos /= 2.54;
+      pos *= 72;
+      pos(1:2:end) /= sz(1);
+      pos(2:2:end) /= sz(2);
+    case "inches"
+      pos *= 72;
+      pos(1:2:end) /= sz(1);
+      pos(2:2:end) /= sz(2);
+    case "pixels"
+      pos /= get (0, "screenpixelsperinch");
+      pos *= 72;
+      pos(1:2:end) /= sz(1);
+      pos(2:2:end) /= sz(2);
+  endswitch
+
+  ## Then convert to requested coordinates
+  switch to
+    case "centimeters"
+      sz /= 72;
+      sz *= 2.54;
+      pos(1:2:end) *= sz(1);
+      pos(2:2:end) *= sz(2);
+    case "inches"
+      sz /= 72;
+      pos(1:2:end) *= sz(1);
+      pos(2:2:end) *= sz(2);
+    case "pixels"
+      sz /= 72;
+      sz *= get (0, "screenpixelsperinch"); 
+      pos(1:2:end) *= sz(1);
+      pos(2:2:end) *= sz(2);
+  endswitch
+
+endfunction
+
+function pos = getnormpos (h)
+  units = get (h, "units");
+  pos = convertposition (h, units, "normalized");
+endfunction
+
+function [x, y] = pos2rect (pos)
+  x = [pos(1) pos(1)+pos(3) pos(1)+pos(3) pos(1)];
+  y = [pos(2) pos(2) pos(2)+pos(4) pos(2)+pos(4)];
+endfunction
+
+function [x, y] = pos2ell (pos)
+  a = pos(3)/2;
+  b = pos(4)/2;
+
+  ## Arbitrarily use 100 points 
+  ## when it is spread over
+  ang = linspace (0, 2*pi, 100);
+  
+  x = a * cos (ang);
+  y = b * sin (ang);
+
+  x += pos(1) + a;
+  y += pos(2) + b;
+endfunction
+
+function update_rect (h, dummy, prop, hre, typ)
+  persistent recursive = false;
+  
+  if (! recursive)
+    switch prop
+      case "position"
+        pos = getnormpos (h);
+        if (strcmp (typ, "rectangle"))
+          [x, y] = pos2rect (pos);
+        else
+          [x, y] = pos2ell (pos);
+        endif
+        
+        set (hre, "xdata", x, "ydata", y);
+      otherwise
+        set (hre, prop, get (h, prop));
+    endswitch
+  endif
+endfunction
+
+
+## FIXME: the additionnal regular axes is necessary for fltk to draw the
+##        annotation axes.
+%!demo
+%! clf; axes ('visible', 'off');
+%! annotation ('ellipse', [.2 .2 .6 .6], 'linewidth', 4)
+%! ang = pi/2:-pi/2:-pi;
+%! lab = {'N', 'W', 'S', 'E'};
+%! x0 = 0.5;
+%! y0 = 0.5;
+%! r = 0.3;
+%! for ii = 1:4
+%!   x = r * cos (ang(ii)) + x0;
+%!   y = r * sin (ang(ii)) + y0;
+%!   annotation ('textarrow', [x x0], [y y0], ...
+%!               'string', lab{ii},  'fontsize', 20);
+%! end
+%! 
+%! h = annotation ('doublearrow', [x0 x0], [y0-r y0+r], ...
+%!                 'head1style', 'diamond', 'head1length', 60, ...
+%!                 'head2style', 'diamond', 'head2length', 60);
+
+%!demo
+%! clf; axes ('visible', 'off');
+%! plot (1:10);
+%! xlabel ('X-LABEL')
+%! ylabel ('LARGE Y-LABEL', 'fontsize', 20)
+%! title ('FIGURE LAYOUT', 'fontsize', 24)
+%! 
+%! ti = get (gca, 'tightinset');
+%! pos = get (gca, 'position');
+%! pos(1:2) = pos(1:2) - ti(1:2);
+%! pos(3) = pos(3) + ti (1) + ti (3);
+%! pos(4) = pos(4) + ti (2) + ti (4);
+%! 
+%! ht = annotation ('textbox', pos, 'string', ' Position + tighinset', ...
+%!                  'fitboxtotext', 'off', 'linestyle', '--', ...
+%!                  'edgecolor', 'g', 'linewidth', 3, 'color', 'g', ...
+%!                  'verticalalignment', 'bottom', 'fontsize', 15);
+%! 
+%! ho = annotation ('textbox', get (gca, 'outerposition'), ...
+%!                  'string', ' Outerposition','fitboxtotext', 'off', ...
+%!                  'linestyle', '--', 'edgecolor', 'r', ...
+%!                  'linewidth', 3, 'color', 'r', ...
+%!                  'verticalalignment', 'bottom', 'fontsize', 15);
+%! 
+%! hi = annotation ('textbox', get (gca, 'position'), ...
+%!                  'string', ' Position','fitboxtotext', 'off', ...
+%!                  'linestyle', '--', 'edgecolor', 'b', ...
+%!                  'linewidth', 3, 'color', 'b', ...
+%!                  'verticalalignment', 'bottom', 'fontsize', 15);
+
+%!demo
+%! clf; axes ('visible', 'off');
+%! h = annotation ('arrow');
+%! 
+%! %% Get allowed headstyles
+%! styles = set (h, 'headstyle');
+%! delete (h)
+%! 
+%! %% Textbox for the title
+%! annotation ('textbox', [0.1 0 0.8 1], 'string', ...
+%!             '"headstyle" property:', ...
+%!             'backgroundcolor', [0.7 0.7 0.7], 'fontsize', 20, ...
+%!             'fitboxtotext', 'off', 'verticalalignment', 'top', ...
+%!             'horizontalalignment', 'center');
+%! 
+%! %% Textarrows 
+%! ns = numel (styles);
+%! nrows = ceil (ns/2);
+%! dy = 1/nrows;
+%! y = 1 - dy/2;
+%! 
+%! jj = 1;
+%! for ii = 1:nrows
+%!   annotation ('textarrow', [0.3 0.5], [y y], ...
+%!               'string', ['"' styles{jj} '"'], 'fontsize', 15, ...
+%!               'headstyle', styles{jj}, 'textcolor', 'b');
+%!   jj = jj + 1;
+%!   if (jj <= ns)
+%!     annotation ('textarrow', [0.7 0.5], [y y], ...
+%!                 'string', ['"' styles{jj} '"'], 'fontsize', 15, ...
+%!                 'headstyle', styles{jj}, 'textcolor', 'b');
+%!   jj = jj + 1;
+%!   end
+%!   y = y - dy;
+%! end
+%! annotation ('line', [0.5 0.5], [dy/2 1-dy/2], 'linestyle', '-.')
+
+%!demo
+%! clf; axes ('visible', 'off');
+%! 
+%! %% Textbox for the title
+%! annotation ('textbox', [0.1 0 0.8 1], 'string', ...
+%!             'Text arrows: text rotation', ...
+%!             'backgroundcolor', [0.7 0.7 0.7], 'fontsize', 20, ...
+%!             'fitboxtotext', 'off', 'verticalalignment', 'top', ...
+%!             'horizontalalignment', 'center');
+%! 
+%! %% Textarrows 
+%! for ii = 1:10
+%!   rot = floor (rand (1) * 360 / 90) * 90;
+%!   annotation ('textarrow', 0.5 + [(0.6 * (rand(1) - .5)) 0], ...
+%!               0.5 + [(0.6 * (rand(1) - .5)) 0], ...
+%!               'string', 'A text', ...
+%!               'headstyle', 'none', 'textrotation', rot);
+%! end
+
+%!demo
+%! clf; axes ('visible', 'off');
+%! 
+%! %% Textbox for the title
+%! annotation ('textbox', [0.1 0 0.8 1], 'string', ...
+%!             'Text arrows: text alignment', ...
+%!             'backgroundcolor', [0.7 0.7 0.7], 'fontsize', 20, ...
+%!             'fitboxtotext', 'off', 'verticalalignment', 'top', ...
+%!             'horizontalalignment', 'center');
+%! 
+%! %% Textarrows
+%! halig = {'right', 'center', 'left'};
+%! ii = 1;
+%! for x = .3:.2:.7
+%!   annotation ('textarrow', [x .5], [.5 .9], ...
+%!               'string', {'Multiple lines', 'text'}, ...
+%!               'headstyle', 'none', 'horizontalalignment', halig{ii});
+%!   ii = ii + 1;
+%! end
+
+%!demo
+%! clf; axes ('visible', 'off');
+%! 
+%! x = 0:0.01:2*pi;
+%! y = sin (x);
+%! plot (x, y)
+%! 
+%! %% Extrema
+%! x0 = [pi/2 3*pi/2];
+%! y0 = [1 -1];
+%! 
+%! %% Convert axes coordinates into normalized coordinates
+%! xl = xlim ();
+%! yl = [-1.2 1.5];
+%! ylim (yl);
+%! x0 = (x0 - xl(1)) / diff(xl);
+%! y0 = (y0 - yl(1)) / diff(yl);
+%! 
+%! pos = get (gca (), 'position');
+%! x0 = x0*pos(3) + pos(1);
+%! y0 = y0*pos(4) + pos(2);
+%! 
+%! 
+%! %% Textarrows
+%! for ii = 1:2
+%!   annotation ('doublearrow', [(x0(ii) - .05) (x0(ii) + .05)], ...
+%!               [y0(ii) y0(ii)], 'head1style', 'vback3', ...
+%!               'head2style', 'vback3', ...
+%!               'head1width', 4, 'head2width', 4)
+%!   h = annotation ('textarrow', [0.5 x0(ii)], [.85 y0(ii)], ...
+%!                   'linestyle', '--', 'headstyle', 'none');
+%! end
+%! set (h, 'string', 'Extrema', 'fontsize', 15) 
+
+## test line properties
+%!test
+%! hf = figure ("visible", "off");
+%! hax = axes ();
+%! unwind_protect
+%!   h = annotation ("line", [0.2 0.7], [0.2 0.7], "linewidth", 2,
+%!                   "linestyle", "--", "color", "r");
+%!   hli = get (h, "children");
+%!   assert (get (hli, "linewidth"), 2);
+%!   assert (get (hli, "linestyle"), "--");
+%!   assert (get (hli, "color"), [1 0 0]);
+%!   assert (gca (), hax);
+%! unwind_protect_cleanup
+%!   close (hf)
+%! end_unwind_protect
+
+## test textarrow properties
+%!test
+%! hf = figure ("visible", "off");
+%! unwind_protect
+%!   h = annotation ("textarrow", [0.2 0.7], [0.2 0.7],
+%!                   "string", "Hello!", "fontsize", 20, 
+%!                   "textrotation", 90, "textcolor", "r");
+%!   hte = findobj (h, "-depth", 1, "type", "text");
+%!   assert (get (hte, "string"), "Hello!");
+%!   assert (get (hte, "fontsize"), 20);
+%!   assert (get (hte, "rotation"), 90);
+%!   assert (get (hte, "color"), [1 0 0]);
+%! unwind_protect_cleanup
+%!   close (hf)
+%! end_unwind_protect
+
+## test textbox properties
+%!test
+%! hf = figure ("visible", "off");
+%! unwind_protect
+%!   h = annotation ("textbox", [0.2 0.2 0.7 0.3], "string", "Hello!",
+%!                   "horizontalalignment", "left", 
+%!                   "verticalalignment", "bottom",
+%!                   "backgroundcolor", "r");
+%!   hte = findobj (h, "-depth", 1, "type", "text");
+%!   hpa = findobj (h, "-depth", 1, "type", "patch");
+%!   assert (get (hte, "string"), "Hello!");
+%!   assert (get (hte, "verticalalignment"), "bottom");
+%!   assert (get (hte, "horizontalalignment"), "left");
+%!   assert (get (hpa, "facecolor"), [1 0 0]);
+%! unwind_protect_cleanup
+%!   close (hf)
+%! end_unwind_protect
+
+## test units conversion
+%!test
+%! hf = figure ("visible", "off");
+%! unwind_protect
+%!   h = annotation ("arrow", [0.1 0.2],  [0.5 0.2]);
+%!   set (h, "units", "inches");
+%!   pos = get (h, "position");
+%!   x = get (h, "x");
+%!   y = get (h, "y");
+%!   set (h, "units", "centimeters");
+%!   assert (get (h, "position"), pos * 2.54, 20*eps);
+%!   assert (get (h, "x"), x * 2.54, 20*eps);
+%!   assert (get (h, "y"), y * 2.54, 20*eps);
+%! unwind_protect_cleanup
+%!   close (hf)
+%! end_unwind_protect
+
+## test annotated figure
+%!test
+%! hf1 = figure ("visible", "off");
+%! hf2 = figure ("visible", "off");
+%! unwind_protect
+%!   h = annotation (hf1, "doublearrow");
+%!   assert (ancestor (h, "figure"), hf1);
+%!   assert (gcf (), hf2);
+%! unwind_protect_cleanup
+%!   close (hf1)
+%!   close (hf2)
+%! end_unwind_protect
+  
+## Test input validation
+%!error <unknown annotation type foo> annotation ("foo")
+%!error annotation ([], "foo")
+%!error annotation ({})
+%!error annotation ("line", [.5 .6])
+%!error <expect 2 elements vectors for X and Y> annotation ("line", 1:3, 1:3)
+%!error <expect 4 elements vector for POS> annotation ("textbox", 1:3)
--- a/scripts/plot/draw/module.mk	Tue Feb 17 15:24:05 2015 -0800
+++ b/scripts/plot/draw/module.mk	Sun Feb 08 22:22:28 2015 +0100
@@ -18,6 +18,7 @@
   plot/draw/private/__stem__.m
 
 plot_draw_FCN_FILES = \
+  plot/draw/annotation.m \
   plot/draw/area.m \
   plot/draw/barh.m \
   plot/draw/bar.m \
--- a/scripts/plot/util/axes.m	Tue Feb 17 15:24:05 2015 -0800
+++ b/scripts/plot/util/axes.m	Sun Feb 08 22:22:28 2015 +0100
@@ -59,20 +59,40 @@
     htmp = varargin{1};
     if (isscalar (htmp) && isaxes (htmp))
       if (__is_handle_visible__ (htmp))
-        parent = ancestor (htmp, "figure");
-        set (0, "currentfigure", parent);
-        set (parent, "currentaxes", htmp);
+        cf = ancestor (htmp, "figure");
+        set (0, "currentfigure", cf);
+        set (cf, "currentaxes", htmp);
 
         ## restack
-        ch = get (parent, "children")(:);
+        ch = get (cf, "children")(:);
         idx = (ch == htmp);
         ch = [ch(idx); ch(!idx)];
-        set (parent, "children", ch);
+        set (cf, "children", ch);
       endif
     else
       error ("axes: H must be a scalar axes handle");
     endif
   endif
+  
+  ## FIXME: In order to have the overlay axes on top of all other axes
+  ##        we restack the figure children. Does Matlab use a similar
+  ##        hack?
+  show = get (0, "showhiddenhandles");
+  set (0, "showhiddenhandles", "on");
+  unwind_protect
+    ch = get (cf, "children");
+    idx = strcmp (get (ch, "tag"), "scribeoverlay");
+    hover = ch(idx);
+    if (! isempty (hover))
+      hax = ch(isaxes (ch));
+      if (numel (hax) > 1)
+        ch(isaxes (ch)) = [hover; hax(hax != hover)];
+        set (cf, "children", ch);
+      endif
+    endif
+  unwind_protect_cleanup
+    set (0, "showhiddenhandles", show);
+  end_unwind_protect
 
   if (nargout > 0)
     h = htmp;
--- a/scripts/plot/util/clf.m	Tue Feb 17 15:24:05 2015 -0800
+++ b/scripts/plot/util/clf.m	Sun Feb 08 22:22:28 2015 +0100
@@ -76,6 +76,10 @@
   else
     ## Select only the chilren with visible handles.
     delete (get (hfig, "children"));
+    
+    ## Also delete the annotation axes
+    hover = findall (hfig, "-depth", 1, "tag", "scribeoverlay");
+    delete (hover);
   endif
 
   if (nargout > 0)
@@ -106,6 +110,7 @@
 %! end_unwind_protect
 
 %!xtest
+%! set (0, "defaultfigurevisible", "off")
 %! hf = figure ("visible", "off");
 %! unwind_protect
 %!   plot (1:10);
@@ -119,6 +124,7 @@
 %!   assert (isempty (get (gcf, "children")));
 %!   assert (get (hf, "papertype"), "usletter");
 %! unwind_protect_cleanup
+%!   set (0, "defaultfigurevisible", "remove")
 %!   close (hf);
 %! end_unwind_protect