changeset 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 6c62150b454a
children 8a303b01cdd0
files scripts/plot/bar.m scripts/plot/barh.m scripts/plot/private/__bar__.m
diffstat 3 files changed, 207 insertions(+), 134 deletions(-) [+]
line wrap: on
line diff
--- a/scripts/plot/bar.m	Sun Sep 29 18:14:31 2013 -0400
+++ b/scripts/plot/bar.m	Mon Sep 30 16:50:41 2013 -0700
@@ -36,8 +36,24 @@
 ## If @var{y} is a matrix, then each column of @var{y} is taken to be a
 ## separate bar graph plotted on the same graph.  By default the columns
 ## are plotted side-by-side.  This behavior can be changed by the @var{style}
-## argument, which can take the values @qcode{"grouped"} (the default),
-## or @qcode{"stacked"}.
+## argument which can take the following values:
+##
+## @table @asis
+## @item @qcode{"grouped"} (default) 
+## Side-by-side bars with a gap between bars and centered over the X-coordinate.
+## 
+## @item  @qcode{"stacked"}
+## Bars are stacked so that each X value has a single bar composed of
+## multiple segments.
+##
+## @item @qcode{"hist"}
+## Side-by-side bars with no gap between bars and centered over the
+## X-coordinate.
+##
+## @item @qcode{"histc"}
+## Side-by-side bars with no gap between bars and left-aligned to the
+## X-coordinate.
+## @end table
 ##
 ## Optional property/value pairs are passed directly to the underlying patch
 ## objects.
@@ -107,13 +123,18 @@
 %! y = rand (11, 1);
 %! h = bar (y);
 %! set (h, 'ydata', sort (rand (11, 1)));
-%! title ('bar() graph')
+%! title ('bar() graph');
 
 %!demo
 %! clf;
 %! h = bar (rand (5, 3));
-%! set (h(1), 'facecolor', 'r')
-%! set (h(2), 'facecolor', 'g')
-%! set (h(3), 'facecolor', 'b')
-%! title ('bar() graph w/multiple bars')
+%! set (h(1), 'facecolor', 'r');
+%! set (h(2), 'facecolor', 'g');
+%! set (h(3), 'facecolor', 'b');
+%! title ('bar() graph w/multiple bars');
 
+%!demo
+%! clf;
+%! h = bar (rand (5, 3), 'stacked');
+%! title ('bar() graph with stacked style');
+
--- a/scripts/plot/barh.m	Sun Sep 29 18:14:31 2013 -0400
+++ b/scripts/plot/barh.m	Mon Sep 30 16:50:41 2013 -0700
@@ -36,8 +36,24 @@
 ## If @var{y} is a matrix, then each column of @var{y} is taken to be a
 ## separate bar graph plotted on the same graph.  By default the columns
 ## are plotted side-by-side.  This behavior can be changed by the @var{style}
-## argument, which can take the values @qcode{"grouped"} (the default),
-## or @qcode{"stacked"}.
+## argument which can take the following values:
+##
+## @table @asis
+## @item @qcode{"grouped"} (default) 
+## Side-by-side bars with a gap between bars and centered over the Y-coordinate.
+## 
+## @item  @qcode{"stacked"}
+## Bars are stacked so that each Y value has a single bar composed of
+## multiple segments.
+##
+## @item @qcode{"hist"}
+## Side-by-side bars with no gap between bars and centered over the
+## Y-coordinate.
+##
+## @item @qcode{"histc"}
+## Side-by-side bars with no gap between bars and left-aligned to the
+## Y-coordinate.
+## @end table
 ##
 ## Optional property/value pairs are passed directly to the underlying patch
 ## objects.
--- 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