changeset 12685:5cbf660e649d

Restructure subplot positioning and avoid labels overlap (bug #31610) * graphics.h.in (axes::properties): New hidden radio property "autopos_tag" enabling special handling of subplot axes. (axes::properties::sync_positions): New function variant accepting looseinset values as input. * graphics.cc (axes::properties::sync_positions): Handle position synchronization of subplots. * subplot.m: Support subplot position synchronization for fltk plots (fixes bug #31610) and simplify the source code. * plotyy.m: Allow "outerposition" as "activepositionproperty" and take looseinset into account.
author Konstantinos Poulios <logari81@googlemail.com>
date Fri, 20 May 2011 15:19:18 +0200
parents abfcb5d5641b
children 6d4c18565de1
files scripts/plot/plotyy.m scripts/plot/subplot.m src/graphics.cc src/graphics.h.in
diffstat 4 files changed, 218 insertions(+), 82 deletions(-) [+]
line wrap: on
line diff
--- a/scripts/plot/plotyy.m	Fri May 20 11:39:06 2011 +0200
+++ b/scripts/plot/plotyy.m	Fri May 20 15:19:18 2011 +0200
@@ -78,7 +78,7 @@
     ca = get (f, "currentaxes");
     if (isempty (ca))
       ax = [];
-    elseif (strcmp (get (ca, "tag"), "plotyy"));
+    elseif (strcmp (get (ca, "tag"), "plotyy"))
       ax = get (ca, "__plotyy_axes__");
     else
       ax = ca;
@@ -113,8 +113,6 @@
     endif
   end_unwind_protect
 
-  set (ax, "activepositionproperty", "position");
-
   if (nargout > 0)
     Ax = ax;
     H1 = h1;
@@ -162,10 +160,24 @@
   colors = get (ax(1), "colororder");
   set (ax(2), "colororder", [colors(2:end,:); colors(1,:)]);
 
+  if (strcmp (get (ax(1), "autopos_tag"), "subplot"))
+    set (ax(2), "autopos_tag", "subplot");
+  else
+    set (ax, "activepositionproperty", "position");
+  endif
+
   h2 = feval (fun2, x2, y2);
   set (ax(2), "yaxislocation", "right");
   set (ax(2), "ycolor", getcolor (h2(1)));
-  set (ax(2), "position", get (ax(1), "position"));
+
+
+  if (strcmp (get(ax(1), "activepositionproperty"), "position"))
+    set (ax(2), "position", get (ax(1), "position"));
+  else
+    set (ax(2), "outerposition", get (ax(1), "outerposition"));
+    set (ax(2), "looseinset", get (ax(1), "looseinset"));
+  endif
+
   set (ax(2), "xlim", xlim);
   set (ax(2), "color", "none");
   set (ax(2), "box", "off");
@@ -184,6 +196,10 @@
 
   addlistener (ax(1), "position", {@update_position, ax(2)});
   addlistener (ax(2), "position", {@update_position, ax(1)});
+  addlistener (ax(1), "outerposition", {@update_position, ax(2)});
+  addlistener (ax(2), "outerposition", {@update_position, ax(1)});
+  addlistener (ax(1), "looseinset", {@update_position, ax(2)});
+  addlistener (ax(2), "looseinset", {@update_position, ax(1)});
   addlistener (ax(1), "view", {@update_position, ax(2)});
   addlistener (ax(2), "view", {@update_position, ax(1)});
   addlistener (ax(1), "plotboxaspectratio", {@update_position, ax(2)});
@@ -257,17 +273,27 @@
   if (! recursion)
     unwind_protect
       recursion = true;
-      position = get (h, "position");
       view = get (h, "view");
-      plotboxaspectratio = get (h, "plotboxaspectratio");
-      plotboxaspectratiomode = get (h, "plotboxaspectratiomode");
-      oldposition = get (ax2, "position");
       oldview = get (ax2, "view");
+      plotboxaspectratio = get (h, "plotboxaspectratio");
       oldplotboxaspectratio = get (ax2, "plotboxaspectratio");
+      plotboxaspectratiomode = get (h, "plotboxaspectratiomode");
       oldplotboxaspectratiomode = get (ax2, "plotboxaspectratiomode");
-      if (! (isequal (position, oldposition) && isequal (view, oldview)))
-        set (ax2, "position", position, "view", view);
+
+      if (strcmp (get(h, "activepositionproperty"), "position"))
+        position = get (h, "position");
+        oldposition = get (ax2, "position");
+        if (! (isequal (position, oldposition) && isequal (view, oldview)))
+          set (ax2, "position", position, "view", view);
+        endif
+      else
+        outerposition = get (h, "outerposition");
+        oldouterposition = get (ax2, "outerposition");
+        if (! (isequal (outerposition, oldouterposition) && isequal (view, oldview)))
+          set (ax2, "outerposition", outerposition, "view", view);
+        endif
       endif
+
       if (! (isequal (plotboxaspectratio, oldplotboxaspectratio)
              && isequal (plotboxaspectratiomode, oldplotboxaspectratiomode)))
         set (ax2, "plotboxaspectratio", plotboxaspectratio);
--- a/scripts/plot/subplot.m	Fri May 20 11:39:06 2011 +0200
+++ b/scripts/plot/subplot.m	Fri May 20 15:19:18 2011 +0200
@@ -124,7 +124,22 @@
     units = "normalized";
     set (0, "defaultaxesunits", units);
     set (cf, "units", "pixels");
-    pos = subplot_position (rows, cols, index, "position");
+
+    ## FIXME: At the moment we force gnuplot to use the aligned mode
+    ##        which will set "activepositionproperty" to "position".
+    ##        Τhis can yield to text overlap between labels and titles
+    ##        see bug #31610
+    if (strcmp (get (cf, "__graphics_toolkit__"), "gnuplot"))
+      align_axes = true;
+    endif
+
+    if (align_axes)
+      pos = subplot_position (rows, cols, index, "position");
+    elseif (strcmp (get (cf, "__graphics_toolkit__"), "gnuplot"))
+      pos = subplot_position (rows, cols, index, "outerpositiontight");
+    else
+      pos = subplot_position (rows, cols, index, "outerposition");
+    endif
 
     set (cf, "nextplot", "add");
 
@@ -144,7 +159,11 @@
             || strcmp (get (child, "tag"), "colorbar"))
           continue;
         endif
-        objpos = get (child, "position");
+        if (align_axes)
+          objpos = get (child, "position");
+        else
+          objpos = get (child, "outerposition");
+        endif
         if (all (objpos == pos) && ! replace_axes)
           ## If the new axes are in exactly the same position as an
           ## existing axes object, use the existing axes.
@@ -170,14 +189,13 @@
 
     if (found)
       set (cf, "currentaxes", tmp);
+    elseif (align_axes)
+      tmp = axes ("box", "off", "position", pos);
+    elseif (strcmp (get (cf, "__graphics_toolkit__"), "gnuplot"))
+      tmp = axes ("box", "off", "outerposition", pos);
     else
-      outpos = subplot_position (rows, cols, index, "outerposition");
-      tmp = axes ("looseinset", [0 0 0 0], "box", "off",
-                  "outerposition", outpos, "position", pos);
-    endif
-
-    if (align_axes || strcmp (get (cf, "__graphics_toolkit__"), "gnuplot"))
-      set (tmp, "activepositionproperty", "position");
+      tmp = axes ("looseinset", [0 0 0 0], "box", "off", "outerposition", pos,
+                  "autopos_tag", "subplot");
     endif
 
   unwind_protect_cleanup
@@ -193,67 +211,55 @@
 
 function pos = subplot_position (rows, cols, index, position_property)
 
-  defaultaxesposition = get (0, "defaultaxesposition");
-  defaultaxesouterposition = get (0, "defaultaxesouterposition");
-
   if (rows == 1 && cols == 1)
     ## Trivial result for subplot (1,1,1)
     if (strcmpi (position_property, "position"))
-      pos = defaultaxesposition;
+      pos = get (0, "defaultaxesposition");
     else
-      pos = defaultaxesouterposition;
+      pos = get (0, "defaultaxesouterposition");
     endif
     return
   endif
 
-  ## The outer margins surrounding all subplot "positions" are independent of
-  ## the number of rows and/or columns
-  margins.left   = defaultaxesposition(1);
-  margins.bottom = defaultaxesposition(2);
-  margins.right  = 1.0 - margins.left - defaultaxesposition(3);
-  margins.top    = 1.0 - margins.bottom - defaultaxesposition(4);
-
-  ## Fit from Matlab experiments
-  pc = 1 ./ [0.1860, (margins.left + margins.right - 1)];
-  margins.column = 1 ./ polyval (pc , cols);
-  pr = 1 ./ [0.2282, (margins.top + margins.bottom - 1)];
-  margins.row    = 1 ./ polyval (pr , rows);
-
-  ## Calculate the width/height of the subplot axes "position".
-  ## This is also consistent with Matlab
-  width = 1 - margins.left - margins.right - (cols-1)*margins.column;
-  width = width / cols;
-  height = 1 - margins.top - margins.bottom - (rows-1)*margins.row;
-  height = height / rows;
+  if (strcmp (position_property, "outerposition")
+      || strcmp (position_property, "outerpositiontight"))
+    margins.left   = 0.05;
+    margins.bottom = 0.05;
+    margins.right  = 0.05;
+    margins.top    = 0.05;
+    if (strcmp (position_property, "outerpositiontight"))
+      margins.column = 0.;
+      margins.row = 0.;
+    else
+      margins.column = 0.04 / cols;
+      margins.row = 0.04 / rows;
+    endif
+    width = 1 - margins.left - margins.right - (cols-1)*margins.column;
+    width = width / cols;
+    height = 1 - margins.top - margins.bottom - (rows-1)*margins.row;
+    height = height / rows;
+  else
+    defaultaxesposition = get (0, "defaultaxesposition");
 
-  if (strcmp (position_property, "outerposition") )
-    ## Calculate the inset of the position relative to the outerposition
-    ## The outerpositions are assumed to be tiled. Matlab's implementation
-    ## has outerposition overlap.
-    if (rows > 1)
-      ## Title on top and xlabel & xticks on bottom
-      inset.top = margins.row * (1/3);
-      inset.bottom = margins.row * (2/3);
-      ## Matlab behavior is approximately ...
-      % inset.bottom = margins.row;
-    else
-      inset.bottom = margins.bottom;
-      inset.top = margins.top;
-    endif
-    if (cols > 1)
-      ## ylabel & yticks on left and some overhang for xticks on right
-      x = 0.1;
-      inset.right = x * margins.column;
-      inset.left = (1 - x) * margins.column;
-    else
-      inset.left  = margins.left;
-      inset.right = margins.right;
-    endif
-    ## Apply the inset to the geometries for the "position" property.
-    margins.column = margins.column - inset.right - inset.left;
-    margins.row = margins.row - inset.top - inset.bottom;
-    width = width + inset.right + inset.left;
-    height = height + inset.top + inset.bottom;
+    ## The outer margins surrounding all subplot "positions" are independent
+    ## of the number of rows and/or columns
+    margins.left   = defaultaxesposition(1);
+    margins.bottom = defaultaxesposition(2);
+    margins.right  = 1.0 - margins.left - defaultaxesposition(3);
+    margins.top    = 1.0 - margins.bottom - defaultaxesposition(4);
+
+    ## Fit from Matlab experiments
+    pc = 1 ./ [0.1860, (margins.left + margins.right - 1)];
+    margins.column = 1 ./ polyval (pc , cols);
+    pr = 1 ./ [0.2282, (margins.top + margins.bottom - 1)];
+    margins.row    = 1 ./ polyval (pr , rows);
+
+    ## Calculate the width/height of the subplot axes "position".
+    ## This is also consistent with Matlab
+    width = 1 - margins.left - margins.right - (cols-1)*margins.column;
+    width = width / cols;
+    height = 1 - margins.top - margins.bottom - (rows-1)*margins.row;
+    height = height / rows;
   endif
 
   ## Index offsets from the lower left subplot
@@ -265,12 +271,6 @@
   x0 = xi .* (width + margins.column) + margins.left;
   y0 = yi .* (height + margins.row) + margins.bottom;
 
-  if (strcmp (position_property, "outerposition") )
-    ## Shift from position(1:2) to outerposition(1:2)
-    x0 = x0 - inset.left;
-    y0 = y0 - inset.bottom;
-  endif
-
   if (numel(x0) > 1)
     ## subplot (row, col, m:n)
     x1 = max (x0(:)) + width;
--- a/src/graphics.cc	Fri May 20 11:39:06 2011 +0200
+++ b/src/graphics.cc	Fri May 20 15:19:18 2011 +0200
@@ -3263,13 +3263,119 @@
 void
 axes::properties::sync_positions (void)
 {
+  Matrix ref_linset = looseinset.get ().matrix_value ();
+  if (autopos_tag_is ("subplot"))
+    {
+      graphics_object parent_obj = gh_manager::get_object (get_parent ());
+      if (parent_obj.isa ("figure"))
+        {
+           // FIXME: temporarily changed units should be protected
+           //        from interrupts
+           std::string fig_units = parent_obj.get ("units").string_value ();
+           parent_obj.set ("units", "pixels");
+
+           Matrix ref_outbox = outerposition.get ().matrix_value ();
+           ref_outbox(2) += ref_outbox(0);
+           ref_outbox(3) += ref_outbox(1);
+
+           // Find those subplots that are left, right, bottom and top aligned
+           // with the current subplot
+           Matrix kids = parent_obj.get_properties ().get_children ();
+           std::vector<octave_value> aligned;
+           std::vector<bool> l_aligned, b_aligned, r_aligned, t_aligned;
+           for (octave_idx_type i = 0; i < kids.numel (); i++)
+             {
+               graphics_object go = gh_manager::get_object (kids(i));
+               if (go.isa ("axes"))
+                 {
+                   axes::properties& props =
+                     dynamic_cast<axes::properties&> (go.get_properties ());
+                   if (props.autopos_tag_is("subplot"))
+                     {
+                       Matrix outpos = go.get ("outerposition").matrix_value ();
+                       bool l_align=(std::abs (outpos(0)-ref_outbox(0)) < 1e-15);
+                       bool b_align=(std::abs (outpos(1)-ref_outbox(1)) < 1e-15);
+                       bool r_align=(std::abs (outpos(0)+outpos(2)-ref_outbox(2)) < 1e-15);
+                       bool t_align=(std::abs (outpos(1)+outpos(3)-ref_outbox(3)) < 1e-15);
+                       if (l_align || b_align || r_align || t_align)
+                         {
+                           aligned.push_back(kids(i));
+                           l_aligned.push_back(l_align);
+                           b_aligned.push_back(b_align);
+                           r_aligned.push_back(r_align);
+                           t_aligned.push_back(t_align);
+                           // FIXME: the temporarily deleted tags should be
+                           //        protected from interrupts
+                           props.set_autopos_tag ("none");
+                         }
+                     }
+                 }
+             }
+           // Determine a minimum box which aligns the subplots
+           Matrix ref_box(1, 4, 0.);
+           ref_box(2) = 1.;
+           ref_box(3) = 1.;
+           for (size_t i = 0; i < aligned.size (); i++)
+             {
+               graphics_object go = gh_manager::get_object (aligned[i]);
+               axes::properties& props =
+                 dynamic_cast<axes::properties&> (go.get_properties ());
+               Matrix linset = props.get_looseinset ().matrix_value ();
+               if (l_aligned[i])
+                 linset(0) = std::min (0., linset(0)-0.01);
+               if (b_aligned[i])
+                 linset(1) = std::min (0., linset(1)-0.01);
+               if (r_aligned[i])
+                 linset(2) = std::min (0., linset(2)-0.01);
+               if (t_aligned[i])
+                 linset(3) = std::min (0., linset(3)-0.01);
+               props.set_looseinset (linset);
+               Matrix pos = props.get_position ().matrix_value ();
+               if (l_aligned[i])
+                 ref_box(0) = std::max (ref_box(0), pos(0));
+               if (b_aligned[i])
+                 ref_box(1) = std::max (ref_box(1), pos(1));
+               if (r_aligned[i])
+                 ref_box(2) = std::min (ref_box(2), pos(0)+pos(2));
+               if (t_aligned[i])
+                 ref_box(3) = std::min (ref_box(3), pos(1)+pos(3));
+             }
+           // Set common looseinset values for all aligned subplots and
+           // revert their tag values
+           for (size_t i = 0; i < aligned.size (); i++)
+             {
+               graphics_object go = gh_manager::get_object (aligned[i]);
+               axes::properties& props =
+                 dynamic_cast<axes::properties&> (go.get_properties ());
+               Matrix outpos = props.get_outerposition ().matrix_value ();
+               Matrix linset = props.get_looseinset ().matrix_value ();
+               if (l_aligned[i])
+                 linset(0) = (ref_box(0)-outpos(0))/outpos(2);
+               if (b_aligned[i])
+                 linset(1) = (ref_box(1)-outpos(1))/outpos(3);
+               if (r_aligned[i])
+                 linset(2) = (outpos(0)+outpos(2)-ref_box(2))/outpos(2);
+               if (t_aligned[i])
+                 linset(3) = (outpos(1)+outpos(3)-ref_box(3))/outpos(3);
+               props.set_looseinset (linset);
+               props.set_autopos_tag ("subplot");
+             }
+           parent_obj.set ("units", fig_units);
+        }
+    }
+  else
+    sync_positions (ref_linset);
+}
+
+void
+axes::properties::sync_positions (const Matrix& linset)
+{
   Matrix pos = position.get ().matrix_value ();
   Matrix outpos = outerposition.get ().matrix_value ();
-  Matrix lins = looseinset.get ().matrix_value ();
-  double lratio = lins(0);
-  double bratio = lins(1);
-  double wratio = 1-lins(0)-lins(2);
-  double hratio = 1-lins(1)-lins(3);
+  double lratio = linset(0);
+  double bratio = linset(1);
+  double wratio = 1-linset(0)-linset(2);
+  double hratio = 1-linset(1)-linset(3);
   if (activepositionproperty.is ("outerposition"))
     {
       pos = outpos;
--- a/src/graphics.h.in	Fri May 20 11:39:06 2011 +0200
+++ b/src/graphics.h.in	Fri May 20 15:19:18 2011 +0200
@@ -3318,6 +3318,8 @@
       row_vector_property zmtick h , Matrix ()
       // hidden properties for inset
       array_property looseinset hu , Matrix (1, 4, 0.0)
+      // hidden properties for alignment of subplots
+      radio_property autopos_tag h , "{none}|subplot"
    END_PROPERTIES
 
   protected:
@@ -3411,7 +3413,9 @@
           calc_ticklabels (ztick, zticklabel, zscale.is ("log"));
       }
 
+    void sync_positions (const Matrix& linset);
     void sync_positions (void);
+
     void update_outerposition (void)
     {
       set_activepositionproperty ("outerposition");