view scripts/plot/draw/private/__bar__.m @ 30564:796f54d4ddbf stable

update Octave Project Developers copyright for the new year In files that have the "Octave Project Developers" copyright notice, update for 2021. In all .txi and .texi files except gpl.txi and gpl.texi in the doc/liboctave and doc/interpreter directories, change the copyright to "Octave Project Developers", the same as used for other source files. Update copyright notices for 2022 (not done since 2019). For gpl.txi and gpl.texi, change the copyright notice to be "Free Software Foundation, Inc." and leave the date at 2007 only because this file only contains the text of the GPL, not anything created by the Octave Project Developers. Add Paul Thomas to contributors.in.
author John W. Eaton <jwe@octave.org>
date Tue, 28 Dec 2021 18:22:40 -0500
parents 363fb10055df
children e1788b1a315f
line wrap: on
line source

########################################################################
##
## Copyright (C) 1996-2022 The Octave Project Developers
##
## See the file COPYRIGHT.md in the top-level directory of this
## distribution or <https://octave.org/copyright/>.
##
## This file is part of Octave.
##
## Octave 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.
##
## Octave 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
## <https://www.gnu.org/licenses/>.
##
########################################################################

## -*- texinfo -*-
## @deftypefn {} {} __bar__ (@var{vertical}, @var{func}, @dots{})
## Undocumented internal function.
## @end deftypefn

function varargout = __bar__ (func, vertical, varargin)

  [hax, varargin, nargin] = __plt_get_axis_arg__ (func, varargin{:});

  if (! isnumeric (varargin{1}))
    error ("%s: Y must be numeric", func);
  endif

  width = 0.8;
  group = true;
  stacked = false;
  histc = NA;
  ## BaseValue
  if (strcmp (get (hax, "yscale"), "log"))
    bv = 1;
  else
    bv = 0;
  endif

  if (nargin > 1 && isnumeric (varargin{2}))
    x = varargin{1};
    if (isvector (x))
      x = x(:);
    endif
    y = varargin{2};
    if (isvector (y))
      y = y(:);
    endif
    if (isscalar (y) && ! isscalar (x))
      ## "y" is actually "width" argument
      y = x;
      x = [1:rows(y)]';
      idx = 2;
    else
      if (! isvector (x))
        error ("%s: X must be a vector", func);
      elseif (numel (unique (x)) != numel (x))
        error ("%s: X vector values must be unique", func);
      endif
      idx = 3;
    endif
  else
    y = varargin{1};
    if (isvector (y))
      y = y(:);
    endif
    x = [1:rows(y)]';
    idx = 2;
  endif

  newargs = {};
  have_line_spec = false;
  while (idx <= nargin)
    if (ischar (varargin{idx}) && strcmpi (varargin{idx}, "grouped"))
      group = true;
      idx += 1;
    elseif (ischar (varargin{idx}) && strcmpi (varargin{idx}, "stacked"))
      stacked = true;
      group = false;
      idx += 1;
    elseif (ischar (varargin{idx}) && strcmpi (varargin{idx}, "histc"))
      group = true;
      histc = true;
      idx += 1;
    elseif (ischar (varargin{idx}) && strcmpi (varargin{idx}, "hist"))
      group = true;
      histc = false;
      idx += 1;
    else
      if ((ischar (varargin{idx}) || iscellstr (varargin{idx}))
          && ! have_line_spec)
        [linespec, valid] = __pltopt__ (func, varargin{idx}, false);
        if (valid)
          have_line_spec = true;
          ## FIXME: strange parse error requires semicolon to be spaced
          ##        away from closing ']' on next line.
          newargs = [{"facecolor", linespec.color}, newargs] ;
          idx += 1;
          continue;
        endif
      endif
      if (isscalar (varargin{idx}))
        width = 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)];
        idx += 2;
      endif
    endif
  endwhile

  ishist = islogical (histc);

  ngrp = rows (x);

  if (isvector (y) && ngrp != rows (y))
    y = y.';
  endif
  if (ngrp != rows (y))
    error ("%s: length of X and Y must be equal", func);
  endif

  nbars = columns (y);

  ## Column width is 1 for 'hist*' styles (bars touch).
  if (ishist)
    cwidth = 1;
    if (nbars == 1)
      gwidth = 1;
    else
      gwidth = width^2;
    endif
  elseif (nbars == 1)
    cwidth = 1;
    gwidth = width;
  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)
    gdelta = cutoff * gwidth / nbars;
    cdelta = repmat ((1 - ((1 - cwidth) / 2)) * gdelta, size (x));
  else
    cdelta = repmat (cutoff * gwidth, size (x));
  endif
  x1 = (x - cdelta)(:)';
  x2 = (x + cdelta)(:)';
  xb = repmat ([x1; x1; x2; x2](:), 1, nbars);

  if (group)
    if (ishist && 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
    if (stacked && any (y(:) < 0))
      ypos = (y >= 0);
      yneg = (y <  0);

      y1p =  cumsum (y .* ypos, 2);
      y1n =  cumsum (y .* yneg, 2);
      y1 = y1p .* ypos + y1n .* yneg;

      y0p = [zeros(ngrp,1)+bv, y1p(:,1:end-1)];
      y0n = [zeros(ngrp,1)+bv, y1n(:,1:end-1)];
      y0 = y0p .* ypos + y0n .* yneg;

    else
      y1 = cumsum (y,2);
      y0 = [zeros(ngrp,1)+bv, y1(:,1:end-1)];
    endif
  endif

  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, ngrp, nbars]);
  yb = reshape (yb, [4, ngrp, nbars]);

  if (nargout < 2)
    oldfig = [];
    if (! isempty (hax))
      oldfig = get (0, "currentfigure");
    endif
    unwind_protect
      hax = newplot (hax);

      htmp = bars (hax, ishist, vertical, x, y, xb, yb, gwidth, group,
                   have_line_spec, bv, newargs{:});

      if (! ishold ())
        if (numel (x(:,1)) <= 15 && all (x(:,1) == fix (x(:,1))))
          ## Set manual ticks, rather than relying on autoselection,
          ## when ticks are a small number of integers.
          if (vertical)
            set (hax, "xtick", x(:,1));
          else
            set (hax, "ytick", x(:,1));
          endif
        endif
        if (ishist)
          set (hax, "climmode", "auto");
        else
          ## Hack prevents xlim setting changes when basevalue changes.
          if (vertical)
            set (hax, "xlimmode", "manual");
          else
            set (hax, "ylimmode", "manual");
          endif
        endif
        set (hax, "box", "on", "layer", "top");
      endif
    unwind_protect_cleanup
      if (! isempty (oldfig))
        set (0, "currentfigure", oldfig);
      endif
    end_unwind_protect
    if (nargout == 1)
      varargout{1} = htmp;
    endif
  else
    if (vertical)
      varargout{1} = xb;
      varargout{2} = yb;
    else
      varargout{1} = yb;
      varargout{2} = xb;
    endif
  endif

endfunction

function hglist = bars (hax, ishist, vertical, x, y, xb, yb, width, group, have_color_spec, base_value, varargin)

  hglist = [];
  nbars = columns (y);

  if (ishist)
    ## Special case for Matlab compatibility.  For 'hist', 'histc' arguments,
    ## return Patch objects rather than hggroup Bar object.
    for i = 1:nbars

      if (vertical)
        h = patch (hax, xb(:,:,i), yb(:,:,i),
                   "cdata", i*ones (columns (xb),1), "FaceColor", "flat");
      else
        h = patch (hax, yb(:,:,i), xb(:,:,i),
                   "cdata", i*ones (columns (yb),1), "FaceColor", "flat");
      endif

      if (! isempty (varargin))
        set (h, varargin{:});
      endif

      hglist = [hglist; h];
    endfor

    return;  # return immediately, rest of function is for creating Bar object.
  endif

  ## Code to create hggroup Bar object
  for i = 1:nbars
    hg = hggroup ();
    hglist = [hglist; hg];
    args = __add_datasource__ ("bar", hg, {"x", "y"}, varargin{:});

    if (vertical)
      if (! have_color_spec)
        color = __next_line_color__ ();
        h = patch (hax, xb(:,:,i), yb(:,:,i), "FaceColor", color, "parent", hg);
      else
        h = patch (hax, xb(:,:,i), yb(:,:,i), "cdata", i, "parent", hg);
      endif
    else
      if (! have_color_spec)
        color = __next_line_color__ ();
        h = patch (hax, yb(:,:,i), xb(:,:,i), "FaceColor", color, "parent", hg);
      else
        h = patch (hax, yb(:,:,i), xb(:,:,i), "cdata", i, "parent", hg);
      endif
    endif

    if (i == 1)
      ## Add baseline object the first time through loop
      x_axis_range = get (hax, "xlim");
      h_baseline = __go_line__ (hax, "xdata", x_axis_range,
                                     "ydata", [base_value, base_value],
                                     "color", [0, 0, 0]);
      set (h_baseline, "handlevisibility", "off", "xliminclude", "off",
                       "parent", get (hg, "parent"));
    endif

    ## Setup the hggroup and listeners
    addproperty ("showbaseline", hg, "radio", "{on}|off");
    addproperty ("basevalue", hg, "data", base_value);
    addproperty ("baseline", hg, "data", h_baseline);

    addlistener (hg, "showbaseline", {@show_baseline, "showbl"});
    addlistener (hg, "visible", {@show_baseline, "visib"});
    addlistener (hg, "basevalue", @move_baseline);

    addproperty ("barwidth", hg, "data", width);
    if (group)
      addproperty ("barlayout", hg, "radio", "stacked|{grouped}", "grouped");
    else
      addproperty ("barlayout", hg, "radio", "{stacked}|grouped", "stacked");
    endif
    if (vertical)
      addproperty ("horizontal", hg, "radio", "on|{off}", "off");
    else
      addproperty ("horizontal", hg, "radio", "{on}|off", "on");
    endif

    addlistener (hg, "barwidth", @update_group);
    addlistener (hg, "barlayout", @update_group);
    addlistener (hg, "horizontal", @update_group);

    addproperty ("edgecolor", hg, "patchedgecolor", get (h, "edgecolor"));
    addproperty ("facecolor", hg, "patchfacecolor", get (h, "facecolor"));
    addproperty ("linestyle", hg, "patchlinestyle", get (h, "linestyle"));
    addproperty ("linewidth", hg, "patchlinewidth", get (h, "linewidth"));

    addlistener (hg, "edgecolor", @update_props);
    addlistener (hg, "facecolor", @update_props);
    addlistener (hg, "linestyle", @update_props);
    addlistener (hg, "linewidth", @update_props);

    if (isvector (x))
      addproperty ("xdata", hg, "data", x);
    else
      addproperty ("xdata", hg, "data", x(:, i));
    endif
    addproperty ("ydata", hg, "data", y(:, i));

    addlistener (hg, "xdata", @update_data);
    addlistener (hg, "ydata", @update_data);

    addproperty ("bargroup", hg, "data");
    set (hglist, "bargroup", hglist);

    ## Matlab property, although Octave does not implement it.
    addproperty ("hittestarea", hg, "radio", "on|{off}", "off");

    if (! isempty (args))
      set (hg, args{:});
    endif
  endfor

  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 (hax, "yscale", {@update_basevalue_logscale, hg});
  addlistener (h_baseline, "ydata", @update_baseline);
  addlistener (h_baseline, "visible", @update_baseline);

endfunction

function update_xlim (h, ~)

  kids = get (h, "children");
  xlim = get (h, "xlim");

  for i = 1 : length (kids)
    obj = get (kids(i));
    if (strcmp (obj.type, "hggroup") && isfield (obj, "baseline"))
      if (any (get (obj.baseline, "xdata") != xlim))
        set (obj.baseline, "xdata", xlim);
      endif
    endif
  endfor

endfunction

function update_basevalue_logscale (hax, ~, hg)

  if (strcmp (get (hax, "yscale"), "log"))
    warning ("off", "Octave:negative-data-log-axis", "local");
    if (get (hg, "basevalue") == 0)
      set (hg, "basevalue", 1);
    endif
  else
    if (get (hg, "basevalue") == 1)
      set (hg, "basevalue", 0);
    endif
  endif

endfunction

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));
    if (strcmp (obj.type, "hggroup") && isfield (obj, "baseline")
        && obj.baseline == h)
      set (obj.bargroup, "showbaseline", visible, "basevalue", ydata);
      break;
    endif
  endfor

endfunction

function show_baseline (h, ~, prop = "")
  persistent recursion = false;

  ## Don't allow recursion
  if (! recursion)
    unwind_protect
      recursion = true;
      hlist = get (h, "bargroup");
      if (strcmp (prop, "showbl"))
        showbaseline = get (h, "showbaseline");
        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")))
          showbaseline = "off";
        endif
      endif
      set (get (h, "baseline"), "visible", showbaseline);
    unwind_protect_cleanup
      recursion = false;
    end_unwind_protect
  endif

endfunction

function move_baseline (h, ~)
  persistent recursion = false;

  ## Don't allow recursion
  if (! recursion)
    recursion = true;
    unwind_protect
      b0 = get (h, "basevalue");
      bl = get (h, "baseline");
      set (bl, "ydata", [b0, b0]);

      if (strcmp (get (h, "barlayout"), "grouped"))
        update_data (h);
      endif
    unwind_protect_cleanup
      recursion = false;
    end_unwind_protect
  endif

endfunction

function update_props (h, ~)
  kids = get (h, "children");
  set (kids, {"edgecolor", "linewidth", "linestyle", "facecolor"},
       get (h, {"edgecolor", "linewidth", "linestyle", "facecolor"}));
endfunction

function update_data (h, ~)
  persistent recursion = false;

  ## Don't allow recursion
  if (! recursion)
    unwind_protect
      recursion = true;
      hlist = get (h, "bargroup");
      x = get (h, "xdata");
      if (! isvector (x))
        x = x(:);
      endif
      ydat = get (hlist, "ydata");
      if (iscell (ydat))
        y = cell2mat (ydat.');
      elseif (isvector (ydat))
        y = ydat(:);
      else
        y = ydat;
      endif

      [xb, yb] = bar (x, y, get (h, "barwidth"), get (h, "barlayout"),
                      "basevalue", get (h, "basevalue"));

      vertical = strcmp (get (h, "horizontal"), "off");
      for i = 1:columns (y)
        hp = get (hlist(i), "children");
        if (vertical)
          set (hp, "xdata", xb(:,:,i), "ydata", yb(:,:,i));
        else
          set (hp, "xdata", yb(:,:,i), "ydata", xb(:,:,i));
        endif
      endfor
    unwind_protect_cleanup
      recursion = false;
    end_unwind_protect
  endif

endfunction

function update_group (h, ~)
  persistent recursion = false;

  ## Don't allow recursion
  if (! recursion)
    unwind_protect
      recursion = true;
      hlist = get (h, "bargroup");
      barwidth = get (h, "barwidth");
      barlayout = get (h, "barlayout");
      horizontal = get (h, "horizontal");

      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
  endif

endfunction