diff scripts/plot/private/__bar__.m @ 17519:cc9befe5d271

Overhaul bar family of plot functions. * scripts/plot/bar.m, scripts/plot/barh.m: Document new "hist", "histc" arguments. * scripts/plot/private/__bar__.m: "grouped" bars now default to having a gap between them for Matlab compatibility. Added new style options "hist", "histc". Renamed variable names for clarity. Removed unnecessary for loops in listener functions. Stop recursion in listener functions.
author Rik <rik@octave.org>
date Mon, 30 Sep 2013 16:50:41 -0700
parents 177147bf7b55
children 76614e624818
line wrap: on
line diff
--- a/scripts/plot/private/__bar__.m	Sun Sep 29 18:14:31 2013 -0400
+++ b/scripts/plot/private/__bar__.m	Mon Sep 30 16:50:41 2013 -0700
@@ -25,14 +25,15 @@
 
 function varargout = __bar__ (vertical, func, varargin)
 
-  [hax, varargin, nargs] = __plt_get_axis_arg__ (func, varargin{:});
+  [hax, varargin, nargin] = __plt_get_axis_arg__ (func, varargin{:});
 
   ## Slightly smaller than 0.8 to avoid clipping issue in gnuplot 4.0
   width = 0.8 - 10 * eps;
   group = true;
-  bv = 0;
+  histc = NA;
+  bv = 0;  # BaseValue
 
-  if (nargs > 1 && isnumeric (varargin{2}))
+  if (nargin > 1 && isnumeric (varargin{2}))
     x = varargin{1};
     if (isvector (x))
       x = x(:);
@@ -65,13 +66,21 @@
 
   newargs = {};
   have_line_spec = false;
-  while (idx <= nargs)
+  while (idx <= nargin)
     if (ischar (varargin{idx}) && strcmpi (varargin{idx}, "grouped"))
       group = true;
       idx++;
     elseif (ischar (varargin{idx}) && strcmpi (varargin{idx}, "stacked"))
       group = false;
       idx++;
+    elseif (ischar (varargin{idx}) && strcmpi (varargin{idx}, "histc"))
+      group = true;
+      histc = true; 
+      idx++;
+    elseif (ischar (varargin{idx}) && strcmpi (varargin{idx}, "hist"))
+      group = true;
+      histc = false;
+      idx++;
     else
       if ((ischar (varargin{idx}) || iscellstr (varargin{idx}))
           && ! have_line_spec)
@@ -87,77 +96,116 @@
       endif
       if (isscalar (varargin{idx}))
         width = varargin{idx++};
-      elseif (idx == nargs)
-        newargs = [newargs,varargin(idx++)];
+      elseif (idx == nargin)
+        newargs = [newargs, varargin(idx++)];
       elseif (ischar (varargin{idx})
               && strcmpi (varargin{idx}, "basevalue")
               && isscalar (varargin{idx+1}))
         bv = varargin{idx+1};
         idx += 2;
       else
-        newargs = [newargs,varargin(idx:idx+1)];
+        newargs = [newargs, varargin(idx:idx+1)];
         idx += 2;
       endif
     endif
   endwhile
 
-  xlen = rows (x);
-  ylen = rows (y);
-
-  if (xlen != ylen)
+  ngrp = rows (x);
+  if (ngrp != rows (y))
     error ("%s: length of X and Y must be equal", func);
   endif
   if (any (x(2:end) < x(1:end-1)))
     error ("%s: X vector values must be in ascending order", func);
   endif
 
-  ycols = columns (y);
+  nbars = columns (y);
+
+  ## Column width is 1 for 'hist*' styles.  Otherwise, same as group width.
+  if (nbars == 1)
+    cwidth = 1;
+    gwidth = width;
+  elseif (islogical (histc))
+    cwidth = 1;
+    gwidth = width^2;
+  else
+    cwidth = gwidth = width;
+  endif
+
+  ## Complicated algorithm sizes bars with unitless parameter width.
+  ## If width is 1.0, adjacent bars in a group are touching.
+  ## Otherwise, bar size is cwidth and the remaining space is split evenly on
+  ## either side of the bar.  For the default 0.8, spacing is [0.1 0.8 0.1].
+  ## Groups of bars are spaced by gwidth.  If gwidth is 1.0 then adjacent
+  ## groups will just touch.
   if (numel (x) > 1)
     cutoff = min (diff (double (x))) / 2;
   else
     cutoff = 1;
   endif
   if (group)
-    delta_p = delta_m = repmat (cutoff * width / ycols, size (x));
+    gdelta = cutoff * gwidth / nbars; 
+    cdelta = repmat ((1 - ((1 - cwidth) / 2)) * gdelta, size (x));
   else
-    delta_p = delta_m = repmat (cutoff * width, size (x));
+    cdelta = repmat (cutoff * gwidth, size (x));
   endif
-  x1 = (x - delta_m)(:)';
-  x2 = (x + delta_p)(:)';
-  xb = repmat ([x1; x1; x2; x2](:), 1, ycols);
+  x1 = (x - cdelta)(:)';
+  x2 = (x + cdelta)(:)';
+  xb = repmat ([x1; x1; x2; x2](:), 1, nbars);
 
   if (group)
-    offset = ((delta_p + delta_m) * [-(ycols - 1) / 2 : (ycols - 1) / 2]);
-    xb(1:4:4*ylen,:) += offset;
-    xb(2:4:4*ylen,:) += offset;
-    xb(3:4:4*ylen,:) += offset;
-    xb(4:4:4*ylen,:) += offset;
+    if (islogical (histc) && histc)
+      offset = 2*cdelta * [0:(nbars-1)] + cdelta(1);  # not centered
+    else
+      offset = 2*cdelta * [-(nbars - 1) / 2 : (nbars - 1) / 2];
+    endif
+
+    xb(1:4:4*ngrp,:) += offset + (1-cwidth) / 2 * (2 * gdelta);
+    xb(2:4:4*ngrp,:) += offset + (1-cwidth) / 2 * (2 * gdelta);
+    xb(3:4:4*ngrp,:) += offset - (1-cwidth) / 2 * (2 * gdelta);
+    xb(4:4:4*ngrp,:) += offset - (1-cwidth) / 2 * (2 * gdelta);
+
     y0 = zeros (size (y)) + bv;
     y1 = y;
   else
     y1 = cumsum (y,2);
-    y0 = [zeros(ylen,1)+bv, y1(:,1:end-1)];
+    y0 = [zeros(ngrp,1)+bv, y1(:,1:end-1)];
   endif
 
-  yb = zeros (4*ylen, ycols);
-  yb(1:4:4*ylen,:) = y0;
-  yb(2:4:4*ylen,:) = y1;
-  yb(3:4:4*ylen,:) = y1;
-  yb(4:4:4*ylen,:) = y0;
+  yb = zeros (4*ngrp, nbars);
+  yb(1:4:4*ngrp,:) = y0;
+  yb(2:4:4*ngrp,:) = y1;
+  yb(3:4:4*ngrp,:) = y1;
+  yb(4:4:4*ngrp,:) = y0;
 
-  xb = reshape (xb, [4, numel(xb) / 4 / ycols, ycols]);
-  yb = reshape (yb, [4, numel(yb) / 4 / ycols, ycols]);
+  xb = reshape (xb, [4, ngrp, nbars]);
+  yb = reshape (yb, [4, ngrp, nbars]);
 
   if (nargout < 2)
-  oldfig = [];
-  if (! isempty (hax))
-    oldfig = get (0, "currentfigure");
-  endif
+    oldfig = [];
+    if (! isempty (hax))
+      oldfig = get (0, "currentfigure");
+    endif
     unwind_protect
       hax = newplot (hax);
 
-      htmp = bars (hax, vertical, x, y, xb, yb, width, group,
+      htmp = bars (hax, vertical, x, y, xb, yb, gwidth, group,
                    have_line_spec, bv, newargs{:});
+
+      if (! ishold (hax))
+        if (all (x(:,1) == fix (x(:,1))))
+          if (vertical)
+            set (hax, "xtick", x(:,1));
+          else
+            set (hax, "ytick", x(:,1));
+          endif
+        endif
+        ## Hack prevents color and xlim setting changes when basevalue changes.
+        if (vertical)
+          set (hax, "clim", [0 1], "xlimmode", "manual");
+        else
+          set (hax, "clim", [0 1], "ylimmode", "manual");
+        endif
+      endif
     unwind_protect_cleanup
       if (! isempty (oldfig))
         set (0, "currentfigure", oldfig);
@@ -178,52 +226,50 @@
 
 endfunction
 
-function tmp = bars (ax, vertical, x, y, xb, yb, width, group, have_color_spec, base_value, varargin)
+function hglist = bars (hax, vertical, x, y, xb, yb, width, group, have_color_spec, base_value, varargin)
 
-  ycols = columns (y);
-  clim = get (ax, "clim");
-  tmp = [];
+  nbars = columns (y);
+  clim = get (hax, "clim");
+  hglist = [];
 
-  for i = 1:ycols
+  for i = 1:nbars
     hg = hggroup ();
-    tmp = [tmp; hg];
+    hglist = [hglist; hg];
     args = __add_datasource__ ("bar", hg, {"x", "y"}, varargin{:});
 
     if (vertical)
       if (! have_color_spec)
-        if (ycols == 1)
+        if (nbars == 1)
           lev = clim(1);
         else
-          lev = (i - 1) * (clim(2) - clim(1)) / (ycols - 1) - clim(1);
+          lev = (i - 1) * (clim(2) - clim(1)) / (nbars - 1) - clim(1);
         endif
-        h = patch (ax, xb(:,:,i), yb(:,:,i), "FaceColor", "flat",
-                   "cdata", lev, "parent", hg);
+        h = patch (hax, xb(:,:,i), yb(:,:,i),
+                        "FaceColor", "flat", "cdata", lev, "parent", hg);
       else
-        h = patch (ax, xb(:,:,i), yb(:,:,i), "parent", hg);
+        h = patch (hax, xb(:,:,i), yb(:,:,i), "parent", hg);
       endif
     else
       if (! have_color_spec)
-        if (ycols == 1)
+        if (nbars == 1)
           lev = clim(1);
         else
-          lev = (i - 1) * (clim(2) - clim(1)) / (ycols - 1) - clim(1);
+          lev = (i - 1) * (clim(2) - clim(1)) / (nbars - 1) - clim(1);
         endif
-        h = patch (ax, yb(:,:,i), xb(:,:,i), "FaceColor", "flat",
-                   "cdata", lev, "parent", hg);
+        h = patch (hax, yb(:,:,i), xb(:,:,i),
+                        "FaceColor", "flat", "cdata", lev, "parent", hg);
       else
-        h = patch (ax, yb(:,:,i), xb(:,:,i), "parent", hg);
+        h = patch (hax, yb(:,:,i), xb(:,:,i), "parent", hg);
       endif
     endif
 
     if (i == 1)
-      x_axis_range = get (ax, "xlim");
-      h_baseline = line (ax, x_axis_range, [base_value, base_value],
-                         "color", [0, 0, 0]);
-      set (h_baseline, "handlevisibility", "off");
-      set (h_baseline, "xliminclude", "off");
-      addlistener (ax, "xlim", @update_xlim);
-      addlistener (h_baseline, "ydata", @update_baseline);
-      addlistener (h_baseline, "visible", @update_baseline);
+      ## Add baseline object the first time through loop
+      x_axis_range = get (hax, "xlim");
+      h_baseline = line (hax, x_axis_range, [base_value, base_value],
+                             "color", [0, 0, 0]);
+      set (h_baseline, "handlevisibility", "off", "xliminclude", "off");
+      set (h_baseline, "parent", get (hg, "parent"));
     endif
 
     ## Setup the hggroup and listeners
@@ -252,14 +298,14 @@
     addlistener (hg, "horizontal", @update_group);
 
     addproperty ("edgecolor", hg, "patchedgecolor", get (h, "edgecolor"));
-    addproperty ("linewidth", hg, "patchlinewidth", get (h, "linewidth"));
+    addproperty ("facecolor", hg, "patchfacecolor", get (h, "facecolor"));
     addproperty ("linestyle", hg, "patchlinestyle", get (h, "linestyle"));
-    addproperty ("facecolor", hg, "patchfacecolor", get (h, "facecolor"));
+    addproperty ("linewidth", hg, "patchlinewidth", get (h, "linewidth"));
 
     addlistener (hg, "edgecolor", @update_props);
-    addlistener (hg, "linewidth", @update_props);
+    addlistener (hg, "facecolor", @update_props);
     addlistener (hg, "linestyle", @update_props);
-    addlistener (hg, "facecolor", @update_props);
+    addlistener (hg, "linewidth", @update_props);
 
     if (isvector (x))
       addproperty ("xdata", hg, "data", x);
@@ -272,24 +318,27 @@
     addlistener (hg, "ydata", @update_data);
 
     addproperty ("bargroup", hg, "data");
-    set (tmp, "bargroup", tmp);
+    set (hglist, "bargroup", hglist);
     if (! isempty (args))
       set (hg, args{:});
     endif
-    if (i == 1)
-      set (h_baseline, "parent", get (hg, "parent"));
-    endif
   endfor
 
-  update_xlim (ax, []);
+  update_xlim (hax, []);
+  ## Add listeners outside of for loop to prevent constant updating during
+  ## creation of plot when patch objects are added.
+  addlistener (hax, "xlim", @update_xlim);
+  addlistener (h_baseline, "ydata", @update_baseline);
+  addlistener (h_baseline, "visible", @update_baseline);
+
 endfunction
 
-function update_xlim (h, d)
+function update_xlim (h, ~)
   kids = get (h, "children");
   xlim = get (h, "xlim");
 
   for i = 1 : length (kids)
-    obj = get (kids (i));
+    obj = get (kids(i));
     if (strcmp (obj.type, "hggroup") && isfield (obj, "baseline"))
       if (any (get (obj.baseline, "xdata") != xlim))
         set (obj.baseline, "xdata", xlim);
@@ -298,27 +347,23 @@
   endfor
 endfunction
 
-function update_baseline (h, d)
+function update_baseline (h, ~)
   visible = get (h, "visible");
   ydata = get (h, "ydata")(1);
 
+  ## Search axis for a bargroup that contains this baseline handle
   kids = get (get (h, "parent"), "children");
   for i = 1 : length (kids)
-    obj = get (kids (i));
+    obj = get (kids(i));
     if (strcmp (obj.type, "hggroup") && isfield (obj, "baseline")
         && obj.baseline == h)
-      ## Only alter if changed to avoid recursion of the listener functions
-      if (! strcmpi (get (kids(i), "showbaseline"), visible))
-        set (kids (i), "showbaseline", visible);
-      endif
-      if (! strcmpi (get (kids(i), "basevalue"), visible))
-        set (kids (i), "basevalue", ydata);
-      endif
+      set (obj.bargroup, "showbaseline", visible, "basevalue", ydata);
+      break;
     endif
   endfor
 endfunction
 
-function show_baseline (h, d, prop = "")
+function show_baseline (h, ~, prop = "")
   persistent recursion = false;
   
   ## Don't allow recursion
@@ -328,11 +373,8 @@
       hlist = get (h, "bargroup");
       if (strcmp (prop, "showbl"))
         showbaseline = get (h, "showbaseline");
-        for hh = hlist(:)'
-          if (hh != h)
-            set (hh, "showbaseline", showbaseline);
-          endif
-        endfor
+        hlist = hlist(hlist != h);  # remove current handle being updated
+        set (hlist, "showbaseline", showbaseline);
       elseif (strcmp (prop, "visib"))
         showbaseline = "on";
         if (all (strcmp (get (hlist, "visible"), "off")))
@@ -346,28 +388,33 @@
   endif
 endfunction
 
-function move_baseline (h, d)
-  b0 = get (h, "basevalue");
-  bl = get (h, "baseline");
+function move_baseline (h, ~)
+  persistent recursion = false;
 
-  if (get (bl, "ydata") != [b0, b0])
-    set (bl, "ydata", [b0, b0]);
-  endif
+  ## Don't allow recursion
+  if (! recursion)
+    recursion = true;
+    unwind_protect
+      b0 = get (h, "basevalue");
+      bl = get (h, "baseline");
+      set (bl, "ydata", [b0, b0]);
 
-  if (strcmpi (get (h, "barlayout"), "grouped"))
-    update_data (h, d);
+      if (strcmp (get (h, "barlayout"), "grouped"))
+        update_data (h);
+      endif
+    unwind_protect_cleanup
+      recursion = false;
+    end_unwind_protect
   endif
 endfunction
 
-function update_props (h, d)
+function update_props (h, ~)
   kids = get (h, "children");
-  set (kids, "edgecolor", get (h, "edgecolor"),
-             "linewidth", get (h, "linewidth"),
-             "linestyle", get (h, "linestyle"),
-             "facecolor", get (h, "facecolor"));
+  set (kids, {"edgecolor", "linewidth", "linestyle", "facecolor"},
+       get (h, {"edgecolor", "linewidth", "linestyle", "facecolor"}));
 endfunction
 
-function update_data (h, d)
+function update_data (h, ~)
   persistent recursion = false;
 
   ## Don't allow recursion
@@ -376,23 +423,23 @@
       recursion = true;
       hlist = get (h, "bargroup");
       x = get (h, "xdata");
-      if (!isvector (x))
+      if (! isvector (x))
         x = x(:);
       endif
-      y = [];
-      for hh = hlist(:)'
-        ytmp = get (hh, "ydata");
-        y = [y ytmp(:)];
-      endfor
+      ydat = get (hlist, "ydata");
+      if (iscell (ydat))
+        y = cell2mat (ydat.');
+      else
+        y = ydat;
+      endif
 
       [xb, yb] = bar (x, y, get (h, "barwidth"), get (h, "barlayout"),
                       "basevalue", get (h, "basevalue"));
-      ny = columns (y);
-      vert = strcmpi (get (h, "horizontal"), "off");
 
-      for i = 1:ny
+      vertical = strcmp (get (h, "horizontal"), "off");
+      for i = 1:columns (y)
         hp = get (hlist(i), "children");
-        if (vert)
+        if (vertical)
           set (hp, "xdata", xb(:,:,i), "ydata", yb(:,:,i));
         else
           set (hp, "xdata", yb(:,:,i), "ydata", xb(:,:,i));
@@ -404,7 +451,7 @@
   endif
 endfunction
 
-function update_group (h, d)
+function update_group (h, ~)
   persistent recursion = false;
 
   ## Don't allow recursion
@@ -416,21 +463,10 @@
       barlayout = get (h, "barlayout");
       horizontal = get (h, "horizontal");
 
-      ## To prevent recursion, only change if modified
-      for hh = hlist(:)'
-        if (hh != h)
-          if (get (hh, "barwidth") != barwidth)
-            set (hh, "barwidth", barwidth);
-          endif
-          if (! strcmpi (get (hh, "barlayout"), barlayout))
-            set (hh, "barlayout", barlayout);
-          endif
-          if (! strcmpi (get (hh, "horizontal"), horizontal))
-            set (hh, "horizontal", horizontal);
-          endif
-        endif
-      endfor
-      update_data (h, d);
+      hlist = hlist(hlist != h);  # remove current handle being updated
+      set (hlist, "barwidth", barwidth, "barlayout", barlayout,
+                  "horizontal", horizontal);
+      update_data (h);
     unwind_protect_cleanup
       recursion = false;
     end_unwind_protect