Mercurial > hg > octave-nkf
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 +++ b/scripts/plot/bar.m @@ -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 +++ b/scripts/plot/barh.m @@ -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 +++ b/scripts/plot/private/__bar__.m @@ -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