view scripts/plot/draw/private/__contour__.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 8afdeac24ba4
children 597f3ee61a48
line wrap: on
line source

########################################################################
##
## Copyright (C) 2007-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 {} {[@var{c}, @var{hg}] =} __contour__ (@dots{})
## Undocumented internal function.
## @end deftypefn

function [c, hg] = __contour__ (varargin)

  ax = varargin{1};
  zlevel = varargin{2};
  filled = "off";

  linespec.color = "auto";
  linespec.linestyle = "-";
  opts = {};
  i = 3;
  while (i <= length (varargin))
    if (ischar (varargin{i}) || iscellstr (varargin{i}))
      arg = varargin{i};
      if (i < length (varargin))
        if (strcmpi (arg, "fill"))
          filled = varargin{i+1};
          varargin(i:i+1) = [];
          continue;
        elseif (strcmpi (arg, "linecolor"))
          linespec.color = varargin{i+1};
          varargin(i:i+1) = [];
          continue;
        endif
      endif

      [lspec, valid] = __pltopt__ ("__contour__", arg, false);
      if (valid)
        varargin(i) = [];
        if (! isempty (lspec.color))
          linespec.color = lspec.color;
        endif
        if (! isempty (lspec.linestyle))
          linespec.linestyle = lspec.linestyle;
        endif
      else  # unrecognized option, pass unmodified in opts cell array
        if (i < length (varargin))
          opts(end+(1:2)) = varargin(i:i+1);
          varargin(i:i+1) = [];
        else
          error ("__contour__: Uneven number of PROP/VAL pairs");
        endif
      endif

    else  # skip numeric arguments
      i += 1;
    endif
  endwhile

  if (length (varargin) < 5)
    z1 = varargin{3};
    x1 = 1 : columns (z1);
    y1 = 1 : rows (z1);
    if (! ismatrix (z1))
      error ("__contour__: Z must be a 2-D matrix");
    endif
  else
    x1 = varargin{3};
    y1 = varargin{4};
    z1 = varargin{5};
    if (! ismatrix (z1) || ! ismatrix (x1) || ! ismatrix (y1))
      error ("__contour__: X, Y, and Z must be matrices");
    endif
    if (isvector (x1))
      if (columns (z1) != length (x1))
        error ("__contour__: size of X must match number of columns in Z");
      endif
    else
      if (! size_equal (x1, z1))
        error ("__contour__: size of X and Z must match");
      endif
    endif
    if (isvector (y1))
      if (rows (z1) != length (y1))
        error ("__contour__: size of Y must match number of rows in Z");
      endif
    else
      if (! size_equal (y1, z1))
        error ("__contour__: size of Y and Z must match");
      endif
    endif
  endif

  if (length (varargin) == 4 || length (varargin) == 6)
    vn = varargin{end};
    vnauto = false;
  else
    vn = 10;
    vnauto = true;
  endif

  if (isscalar (vn))
    ## FIXME: The levels should be determined similarly to {x,y,z}ticks
    ##        so that they aren't set at extremely odd values.
    lvl = linspace (min (z1(! isinf (z1))), max (z1(! isinf (z1))), vn + 2);
    ## Strip off max outlier, min must stay for contourf hole algorithm.
    lvl = lvl(1:end-1);
  else
    lvl = sort (vn);
  endif

  if (strcmpi (filled, "on"))
    if (isvector (x1) || isvector (y1))
      [x1, y1] = meshgrid (x1, y1);
    endif
    [nr, nc] = size (z1);
    x0 = prepad (x1, nc+1, 2 * x1(1, 1) - x1(1, 2), 2);
    x0 = postpad (x0, nc+2, 2 * x1(1, nc) - x1(1, nc - 1), 2);
    x0 = [x0(1, :); x0; x0(1, :)];
    y0 = prepad (y1, nr+1, 2 * y1(1, 1) - y1(2, 1), 1);
    y0 = postpad (y0, nr+2, 2 * y1(nr, 1) - y1(nr - 1, 1));
    y0 = [y0(:, 1), y0, y0(:, 1)];
    z0 = -Inf (nr+2, nc+2);
    z0(2:nr+1, 2:nc+1) = z1;
    [c, lev] = contourc (x0, y0, z0, lvl);
  else
    [c, lev] = contourc (x1, y1, z1, lvl);
  endif

  hg = hggroup ("__appdata__", struct ("__creator__", "__contour__"));
  opts = __add_datasource__ ("__countour__", hg, {"x", "y", "z"}, opts{:});

  addproperty ("xdata", hg, "data", x1);
  addproperty ("ydata", hg, "data", y1);
  addproperty ("zdata", hg, "data", z1);
  addproperty ("contourmatrix", hg, "data", c);

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

  addproperty ("fill", hg, "radio", "on|{off}", filled);

  ## The properties zlevel and zlevelmode don't exist in matlab, but allow the
  ## use of contourgroups with the contour3, meshc, and surfc functions.
  if (isnumeric (zlevel))
    addproperty ("zlevelmode", hg, "radio", "{none}|auto|manual", "manual");
    addproperty ("zlevel", hg, "data", zlevel);
  else
    addproperty ("zlevelmode", hg, "radio", "{none}|auto|manual", zlevel);
    addproperty ("zlevel", hg, "data", 0.);
  endif

  lvlstep = sum (diff (lvl)) / (length (lvl) - 1);

  addproperty ("levellist", hg, "data", lev);
  addproperty ("levelstep", hg, "double", lvlstep);
  if (vnauto)
    addproperty ("levellistmode", hg, "radio", "{auto}|manual", "auto");
    addproperty ("levelstepmode", hg, "radio", "{auto}|manual", "auto");
  elseif (isscalar (vn))
    addproperty ("levellistmode", hg, "radio", "{auto}|manual", "auto");
    addproperty ("levelstepmode", hg, "radio", "{auto}|manual", "manual");
  else
    addproperty ("levellistmode", hg, "radio", "{auto}|manual", "manual");
    addproperty ("levelstepmode", hg, "radio", "{auto}|manual", "auto");
  endif

  addproperty ("labelspacing", hg, "double", 144);
  addproperty ("textlist", hg, "data", lev);
  addproperty ("textlistmode", hg, "radio", "{auto}|manual", "auto");
  addproperty ("textstep", hg, "double", lvlstep);
  addproperty ("textstepmode", hg, "radio", "{auto}|manual", "auto");
  addproperty ("showtext", hg, "radio", "on|{off}", "off");

  addproperty ("linecolor", hg, "color", linespec.color, "{auto}|none");
  addproperty ("linestyle", hg, "linelinestyle", linespec.linestyle);
  addproperty ("linewidth", hg, "linelinewidth", 0.5);

  ## Matlab property just for compatibility (bug #60513).
  addproperty ("facecolor", hg, "patchfacecolor", "none");

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

  addlistener (hg, "fill", {@update_data, "fill"});

  addlistener (hg, "zlevelmode", @update_zlevel);
  addlistener (hg, "zlevel", @update_zlevel);

  addlistener (hg, "levellist", {@update_data, "levellist"});
  addlistener (hg, "levelstep", {@update_data, "levelstep"});
  addlistener (hg, "levellistmode", @update_data);
  addlistener (hg, "levelstepmode", @update_data);

  addlistener (hg, "labelspacing", @update_text);
  addlistener (hg, "textlist", {@update_text, "textlist"});
  addlistener (hg, "textlistmode", @update_text);
  addlistener (hg, "textstep", {@update_text, "textstep"});
  addlistener (hg, "textstepmode", @update_text);
  addlistener (hg, "showtext", @update_text);

  addlistener (hg, "linecolor", @update_line);
  addlistener (hg, "linestyle", @update_line);
  addlistener (hg, "linewidth", @update_line);

  ## Set axis before adding patches so that each new patch does not trigger
  ## new axis calculation.  No need if mode is already "manual".
  if (all (strcmp (get (gca (), {"xlimmode", "ylimmode"}), "auto")))
    axis ([min(x1(:)) max(x1(:)) min(y1(:)) max(y1(:))]);
  endif

  add_patch_children (hg);

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

endfunction

function add_patch_children (hg)

  c = get (hg, "contourmatrix");
  lev = get (hg, "levellist");
  fill = get (hg, "fill");
  zlev = get (hg, "zlevel");
  zmode = get (hg, "zlevelmode");
  lc = get (hg, "linecolor");
  lw = get (hg, "linewidth");
  ls = get (hg, "linestyle");
  filled = get (hg, "fill");
  ca = gca ();

  ## Turn off automatic updating of clim while adding patches
  climmode = get (ca, "climmode");
  set (ca, "climmode", "manual");

  if (strcmp (lc, "auto"))
    lc = "flat";
  endif

  if (strcmp (filled, "on"))

    lvl_eps = get_lvl_eps (lev);

    ## Decode contourc output format.
    i = 1;
    ncont = 0;
    while (i < columns (c))
      ncont += 1;
      cont_lev(ncont) = c(1, i);
      cont_len(ncont) = c(2, i);
      cont_idx(ncont) = i+1;
      ii = i + (1:cont_len(ncont));
      cont_area(ncont) = polyarea (c(1, ii), c(2, ii));
      i += cont_len(ncont) + 1;
    endwhile

    ## Handle for each level the case where we have (a) hole(s) in a patch.
    ## Those are to be filled with the color of level below or with the
    ## background color.
    for k = 1:numel (lev)
      lvl_idx = find (abs (cont_lev - lev(k)) < lvl_eps);
      len = numel (lvl_idx);
      if (len > 1)
        mark = false (size (lvl_idx));
        a = 1;
        while (a < len)
          ## take 1st patch
          pa_idx = lvl_idx(a);
          ## get pointer to contour start, and contour length
          curr_ct_idx = cont_idx(pa_idx);
          curr_ct_len = cont_len(pa_idx);
          ## get contour
          curr_ct = c(:, curr_ct_idx:curr_ct_idx+curr_ct_len-1);
          b_vec = (a+1):len;
          next_ct_pt_vec = c(:, cont_idx(lvl_idx(b_vec)));
          in = inpolygon (next_ct_pt_vec(1,:), next_ct_pt_vec(2,:),
                          curr_ct(1, :), curr_ct(2, :));
          mark(b_vec(in)) = ! mark(b_vec(in));
          a += 1;
        endwhile
        if (numel (mark) > 0)
          ## All marked contours describe a hole in a larger contour of
          ## the same level and must be filled with color of level below.
          ma_idx = lvl_idx(mark);
          if (k > 1)
            ## Find color of level below.
            tmp = find (abs (cont_lev - lev(k - 1)) < lvl_eps);
            lvl_bel_idx = tmp(1);
            ## Set color of patches found.
            cont_lev(ma_idx) = cont_lev(lvl_bel_idx);
          else
            ## Set lowest level contour to NaN.
            cont_lev(ma_idx) = NaN;
          endif
        endif
      endif
    endfor

    ## The algorithm can create patches with the size of the plotting
    ## area, we would like to draw only the patch with the highest level.
    del_idx = [];
    max_idx = find (cont_area == max (cont_area));
    if (numel (max_idx) > 1)
      ## delete double entries
      del_idx = max_idx(1:end-1);
      cont_area(del_idx) = cont_lev(del_idx) = [];
      cont_len(del_idx) = cont_idx(del_idx) = [];
    endif

    ## Now we have everything together and can start plotting the patches
    ## beginning with largest area.
    [~, svec] = sort (cont_area);
    len = ncont - numel (del_idx);
    h = [];
    for n = len:-1:1
      idx = svec(n);
      ctmp = c(:, cont_idx(idx):cont_idx(idx) + cont_len(idx) - 1);
      if (all (ctmp(:,1) == ctmp(:,end)))
        ## patch() doesn't need/want closed contour.  It will do it itself.
        ctmp(:,end) = [];
      endif
      if (isnan (cont_lev(idx)))
        fc = get (ca, "color");
        if (strcmp (fc, "none"))
          fc = get (ancestor (ca, "figure"), "color");
        endif
      else
        fc = "flat";
      endif
      h = [h; __go_patch__(ca, "xdata", ctmp(1, :)(:), "ydata", ctmp(2, :)(:),
                           "vertices", ctmp.', "faces", 1:(cont_len(idx)-1),
                           "facevertexcdata", cont_lev(idx), "facecolor", fc,
                           "cdata", cont_lev(idx), "edgecolor", lc,
                           "linestyle", ls, "linewidth", lw,
                           "parent", hg)];
    endfor

    if (min (lev) == max (lev))
      set (ca, "clim", [min(lev)-1, max(lev)+1], "layer", "top");
    else
      set (ca, "clim", [min(lev), max(lev)], "layer", "top");
    endif
  else
    ## Decode contourc output format.
    h = [];
    i = 1;
    while (i < length (c))
      clev = c(1,i);
      clen = c(2,i);

      if (all (c(:,i+1) == c(:,i+clen)))
        p = c(:, i+1:i+clen-1).';
      else
        p = [c(:, i+1:i+clen), NaN(2, 1)].';
      endif

      switch (zmode)
        case "none"
          h = [h; __go_patch__(ca, "xdata", p(:,1), "ydata", p(:,2),
                               "zdata", [],
                               "vertices", p, "faces", 1:rows (p),
                               "facevertexcdata", clev, "facecolor", "none",
                               "cdata", clev, "edgecolor", lc,
                               "linestyle", ls, "linewidth", lw,
                               "parent", hg)];
        case "auto"
          h = [h; __go_patch__(ca, "xdata", p(:,1), "ydata", p(:,2),
                               "zdata", clev * ones (rows (p),1),
                               "vertices", [p, clev * ones(rows(p),1)],
                               "faces", 1:rows (p),
                               "facevertexcdata", clev, "facecolor", "none",
                               "cdata", clev, "edgecolor", lc,
                               "linestyle", ls, "linewidth", lw,
                               "parent", hg)];
        otherwise
          h = [h; __go_patch__(ca, "xdata", p(:,1), "ydata", p(:,2),
                               "zdata", zlev * ones (rows (p), 1),
                               "vertices", [p, zlev * ones(rows(p),1)],
                               "faces", 1:rows (p),
                               "facevertexcdata", clev, "facecolor", "none",
                               "cdata", clev, "edgecolor", lc,
                               "linestyle", ls, "linewidth", lw,
                               "parent", hg)];
      endswitch
      i += clen + 1;
    endwhile
  endif

  set (ca, "climmode", climmode);

endfunction

function update_zlevel (h, ~)

  z = get (h, "zlevel");
  zmode = get (h, "zlevelmode");
  kids = get (h, "children");

  switch (zmode)
    case "none"
      set (kids, "zdata", []);
    case "auto"
      for i = 1 : length (kids)
        set (kids(i), "zdata", get (kids(i), "cdata") .*
             ones (size (get (kids(i), "xdata"))));
      endfor
    otherwise
      for i = 1 : length (kids)
        set (kids(i), "zdata", z .* ones (size (get (kids(i), "xdata"))));
      endfor
  endswitch

endfunction

function update_line (h, ~)

  lc = get (h, "linecolor");
  if (strcmp (lc, "auto"))
    lc = "flat";
  endif
  set (findobj (h, "type", "patch"), "edgecolor", lc,
       "linewidth", get (h, "linewidth"), "linestyle", get (h, "linestyle"));

endfunction

function update_data (h, ~, prop = "")
  persistent recursive = false;

  if (! recursive)
    recursive = true;

    delete (get (h, "children"));

    switch (prop)
      case "levellist"
        set (h, "levellistmode", "manual");
      case "levelstep"
        set (h, "levelstepmode", "manual");
      case "fill"
        ## Switching from filled ('k' linespec) to unfilled, reset linecolor
        if (strcmp (get (h, "fill"), "off"))
          set (h, "linecolor", "auto");
        else
          set (h, "linecolor", "black");
        endif
    endswitch

    if (strcmp (get (h, "levellistmode"), "manual")
        && ! strcmp (prop, "levelstep"))
      lvl = get (h, "levellist");
    elseif (strcmp (get (h, "levelstepmode"), "manual"))
      z = get (h, "zdata");
      lvs = get (h, "levelstep");
      lvl(1) = ceil (min (z(:)) / lvs) * lvs;
      lvl(2) = floor (max (z(:)) / lvs) * lvs;
      if (lvl(1) >= lvl(2))
        lvl = median (z(:));
      else
        lvl = lvl(1) : lvs : lvl(2);
      endif
      set (h, "levellist", lvl, "levellistmode", "auto");
    else
      z = get (h, "zdata");
      ## FIXME: The levels should be determined similarly to {x,y,z}ticks
      ##        so that they aren't set at extremely odd values.
      lvl = linspace (min (z(! isinf (z))), max (z(! isinf (z))), 10 + 2);
      ## Strip off max outlier, min must stay for contourf hole algorithm.
      lvl = lvl(1:end-1);
    endif

    if (strcmp (get (h, "fill"), "on"))
      X = get (h, "xdata");
      Y = get (h, "ydata");
      Z = get (h, "zdata");
      if (isvector (X) || isvector (Y))
        [X, Y] = meshgrid (X, Y);
      endif
      [nr, nc] = size (Z);
      X0 = prepad (X, nc+1, 2 * X(1, 1) - X(1, 2), 2);
      X0 = postpad (X0, nc+2, 2 * X(1, nc) - X(1, nc - 1), 2);
      X0 = [X0(1, :); X0; X0(1, :)];
      Y0 = prepad (Y, nr+1, 2 * Y(1, 1) - Y(2, 1), 1);
      Y0 = postpad (Y0, nr+2, 2 * Y(nr, 1) - Y(nr - 1, 1));
      Y0 = [Y0(:, 1), Y0, Y0(:, 1)];
      Z0 = -Inf (nr+2, nc+2);
      Z0(2:nr+1, 2:nc+1) = Z;
      [c, lev] = contourc (X0, Y0, Z0, lvl);
    else
      [c, lev] = contourc (get (h, "xdata"), get (h, "ydata"),
                           get (h, "zdata"), lvl);
    endif
    set (h, "contourmatrix", c);

    if (strcmp (get (h, "levellistmode"), "manual"))
      ## Do nothing
    elseif (strcmp (get (h, "levelstepmode"), "manual"))
      set (h, "levellist", lev);
    else
      set (h, "levellist", lev);
      lvlstep = sum (diff (lvl)) / (length (lvl) - 1);
      set (h, "levelstep", lvlstep);
    endif

    add_patch_children (h);
    update_text (h);
    recursive = false;
  endif

endfunction

function update_text (h, ~, prop = "")
  persistent recursive = false;

  if (! recursive)
    recursive = true;

    delete (findobj (h, "type", "text"));

    switch (prop)
      case "textlist"
        set (h, "textlistmode", "manual");
      case "textstep"
        set (h, "textstepmode", "manual");
    endswitch

    if (strcmp (get (h, "textlistmode"), "manual"))
      lvl = get (h, "textlist");
    elseif (strcmp (get (h, "textstepmode"), "manual"))
      lev = get (h, "levellist");

      lvl_eps = get_lvl_eps (lev);

      stp = get (h, "textstep");
      t = [0, floor(cumsum(diff (lev)) / (abs(stp) - lvl_eps))];
      lvl = lev([true, t(1:end-1) != t(2:end)]);
      set (h, "textlist", lvl);
    else
      lvl = get (h, "levellist");
      set (h, "textlist", lvl, "textstep", get (h, "levelstep"));
    endif

    if (strcmp (get (h, "showtext"), "on"))
      switch (get (h, "zlevelmode"))
        case "manual"
          __clabel__ (get (h, "contourmatrix"), lvl, h,
                      get (h, "labelspacing"), get (h, "zlevel"));
        case "auto"
          __clabel__ (get (h, "contourmatrix"), lvl, h,
                      get (h, "labelspacing"), "auto");
        otherwise
          __clabel__ (get (h, "contourmatrix"), lvl, h,
                      get (h, "labelspacing"), []);
      endswitch
    endif

    recursive = false;
  endif

endfunction

function lvl_eps = get_lvl_eps (lev)

  ## FIXME: is this the right thing to do for this tolerance?  Should
  ## it be an absolute or relative tolerance, or switch from one to the
  ## other depending on the value of lev?
  if (isscalar (lev))
    lvl_eps = abs (lev) * sqrt (eps) + sqrt (eps);
  else
    tmp = min (abs (diff (lev)));
    if (tmp < 10*eps)
      lvl_eps = sqrt (eps);
    else
      lvl_eps = tmp / 1000.0;
    endif
  endif

endfunction