view scripts/plot/draw/polar.m @ 19016:87c3848cf3c0

Fix bug when hggroup used with primitive graphic object (bug #42532). * image.m, text.m, line.m, patch.m: __plt_get_axis_arg__ will return axis and hggroup when 'parent' property is used. Select the first returned object which is the axes, rather than passing both axis and hggroup to further plot subroutines.
author Rik <rik@octave.org>
date Tue, 10 Jun 2014 14:03:09 -0700
parents 56bff71de2ca
children cafffc1b70b1
line wrap: on
line source

## Copyright (C) 1993-2013 John W. Eaton
##
## 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
## <http://www.gnu.org/licenses/>.

## -*- texinfo -*-
## @deftypefn  {Function File} {} polar (@var{theta}, @var{rho})
## @deftypefnx {Function File} {} polar (@var{theta}, @var{rho}, @var{fmt})
## @deftypefnx {Function File} {} polar (@var{cplx})
## @deftypefnx {Function File} {} polar (@var{cplx}, @var{fmt})
## @deftypefnx {Function File} {} polar (@var{hax}, @dots{})
## @deftypefnx {Function File} {@var{h} =} polar (@dots{})
## Create a 2-D plot from polar coordinates @var{theta} and @var{rho}.
##
## If a single complex input @var{cplx} is given then the real part is used
## for @var{theta} and the imaginary part is used for @var{rho}.
##
## The optional argument @var{fmt} specifies the line format in the same way
## as @code{plot}.
##
## If the first argument @var{hax} is an axes handle, then plot into this axis,
## rather than the current axes returned by @code{gca}.
##
## The optional return value @var{h} is a graphics handle to the created plot.
##
## Implementation Note: The polar axis is drawn using line and text objects
## encapsulated in an hggroup.  The hggroup properties are linked to the
## original axes object such that altering an appearance property, for example
## @code{fontname}, will update the polar axis.  Two new properties are
## added to the original axes--@code{rtick}, @code{ttick}--which replace
## @code{xtick}, @code{ytick}.  The first is a list of tick locations in the
## radial (rho) direction; The second is a list of tick locations in the
## angular (theta) direction specified in degrees, i.e., in the range 0--359.
## @seealso{rose, compass, plot}
## @end deftypefn

## Author: jwe

function h = polar (varargin)

  [hax, varargin, nargs] = __plt_get_axis_arg__ ("polar", varargin{:});

  if (nargs < 1)
    print_usage ();
  endif

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

    if (nargs == 3)
      if (! ischar (varargin{3}))
        error ("polar: FMT argument must be a string");
      endif
      htmp = __plr2__ (hax, varargin{:});
      maxr = max (varargin{2}(:));
    elseif (nargs == 2)
      if (ischar (varargin{2}))
        htmp = __plr1__ (hax, varargin{:});
        if (iscomplex (varargin{1}))
          maxr = max (imag (varargin{1})(:));
        else
          maxr = max (varargin{1}(:));
        endif
      else
        fmt = "";
        htmp = __plr2__ (hax, varargin{:}, fmt);
        maxr = max (varargin{2}(:));
      endif
    elseif (nargs == 1)
      fmt = "";
      htmp = __plr1__ (hax, varargin{:}, fmt);
      if (iscomplex (varargin{1}))
        maxr = max (imag (varargin{1})(:));
      else
        maxr = max (varargin{1}(:));
      endif
    else
      print_usage ();
    endif

    ## FIXME: Should more gracefully handle "hold on" and not override props.
    set (hax, "visible", "off", "plotboxaspectratio", [1, 1, 1],
              "zlim", [-1 1]);

    if (! isprop (hax, "rtick"))
      addproperty ("rtick", hax, "data");
    endif

    ## calculate r(ho)tick from xtick
    xtick = get (hax, "xtick");
    rtick = xtick(find (xtick > 0, 1):find (xtick >= maxr, 1));
    if (isempty (rtick))
      rtick = [0.5 1];
    endif
    set (hax, "rtick", rtick);

    ## add t(heta)tick
    if (! isprop (hax, "ttick"))
      addproperty ("ttick", hax, "data");
    endif

    ## theta(angular) ticks in degrees
    set (hax, "ttick", 0:30:330);

    ## Create hggroup to hold text/line objects and attach listeners
    hg = hggroup (hax, "tag", "polar_grid", "handlevisibility", "off");
    __update_polar_grid__(hax, [], hg);

    addlistener (hax, "rtick", {@__update_polar_grid__, hg});
    addlistener (hax, "ttick", {@__update_polar_grid__, hg});
    addlistener (hax, "color", {@__update_patch__, hg});
    addlistener (hax, "fontangle", {@__update_text__, hg, "fontangle"});
    addlistener (hax, "fontname", {@__update_text__, hg, "fontname"});
    addlistener (hax, "fontsize", {@__update_text__, hg, "fontsize"});
    addlistener (hax, "fontunits", {@__update_text__, hg, "fontunits"});
    addlistener (hax, "fontweight", {@__update_text__, hg, "fontweight"});
    addlistener (hax, "interpreter", {@__update_text__, hg, "interpreter"});
    addlistener (hax, "layer", {@__update_layer__, hg});
    addlistener (hax, "gridlinestyle", {@__update_lines__, hg,"gridlinestyle"});
    addlistener (hax, "linewidth", {@__update_lines__, hg, "linewidth"});

  unwind_protect_cleanup
    if (! isempty (oldfig))
      set (0, "currentfigure", oldfig);
    endif
  end_unwind_protect

  if (nargout > 0)
    h = htmp;
  endif

endfunction

function retval = __plr1__ (h, theta, fmt)

  theta = theta(:);
  if (iscomplex (theta))
    rho = imag (theta);
    theta = real (theta);
  else
    rho = theta;
    theta = (1:rows (rho))';
  endif

  retval = __plr2__ (h, theta, rho, fmt);

endfunction

function retval = __plr2__ (h, theta, rho, fmt)

  if (ndims (theta) > 2 || ndims (rho) > 2)
    error ("polar: THETA and RHO must be 2-D objects");
  endif
  theta = real (theta);
  rho = real (rho);

  if (isscalar (theta))
    if (isscalar (rho))
      x = rho * cos (theta);
      y = rho * sin (theta);
      retval = __plt__ ("polar", h, x, y, fmt);
    else
      error ("polar: Can't plot constant THETA with varying RHO");
    endif
  elseif (isvector (theta))
    if (isvector (rho))
      if (length (theta) != length (rho))
        error ("polar: THETA and RHO vector lengths must match");
      endif
      rho = rho(:);
      theta = theta(:);
      x = rho .* cos (theta);
      y = rho .* sin (theta);
      retval = __plt__ ("polar", h, x, y, fmt);
    elseif (ismatrix (rho))
      theta = theta(:);
      t_nr = rows (theta);
      [r_nr, r_nc] = size (rho);
      if (t_nr != r_nr)
        rho = rho';
        r_nr = r_nc;
      endif
      if (t_nr != r_nr)
        error ("polar: THETA vector and RHO matrix sizes must match");
      endif
      x = diag (cos (theta)) * rho;
      y = diag (sin (theta)) * rho;
      retval = __plt__ ("polar", h, x, y, fmt);
    else
      error ("polar: invalid data for plotting");
    endif
  elseif (ismatrix (theta))
    if (isvector (rho))
      rho = rho(:);
      r_nr = rows (rho);
      [t_nr, t_nc] = size (theta);
      if (r_nr != t_nr)
        theta = theta';
        t_nr = t_nc;
      endif
      if (r_nr != t_nr)
        error ("polar: THETA matrix and RHO vector sizes must match");
      endif
      diag_r = diag (rho);
      x = diag_r * cos (theta);
      y = diag_r * sin (theta);
      retval = __plt__ ("polar", h, x, y, fmt);
    elseif (ismatrix (rho))
      if (! size_equal (rho, theta))
        error ("polar: THETA and RHO matrix dimensions must match");
      endif
      x = rho .* cos (theta);
      y = rho .* sin (theta);
      retval = __plt__ ("polar", h, x, y, fmt);
    else
      error ("polar: invalid data for plotting");
    endif
  else
    error ("polar: invalid data for plotting");
  endif

endfunction

## Callback functions for listeners

function __update_text__ (hax, ~, hg, prop)

  kids = get (hg, "children");
  idx = strcmp (get (kids, "type"), "text");
  set (kids(idx).', prop, get (hax, prop));

endfunction

function __update_lines__ (hax,  ~, hg, prop)

  kids = get (hg, "children");
  idx = strcmp (get (kids, "type"), "line");
  lprop = prop;
  if (strcmp (prop, "gridlinestyle"))
    lprop = "linestyle";
  endif
  set (kids(idx).', lprop, get (hax, prop));

endfunction

function __update_patch__ (hax, ~, hg)

  kids = get (hg, "children");
  idx = strcmp (get (kids, "type"), "patch");
  set (kids(idx).', "facecolor", get (hax, "color"));

endfunction

function __update_layer__ (hax,  ~, hg)

  set (hg, "handlevisibility", "on");
  kids = get (hax, "children");
  if (strcmp (get (hax, "layer"), "bottom"))
    set (hax, "children", [kids(kids != hg); hg]); 
  else
    set (hax, "children", [hg; kids(kids != hg)]); 
  endif
  set (hg, "handlevisibility", "off");

endfunction

function __update_polar_grid__ (hax, ~, hg)

  ## Delete existing polar grid
  delete (get (hg, "children"));

  rtick = unique (get (hax, "rtick")(:)');
  rtick = rtick(rtick > 0);
  if (isempty (rtick))
    rtick = [0.5 1];
  endif

  ttick = unique (get (hax, "ttick")(:)');
  ttick = ttick(ttick >= 0);
  if (isempty (ttick))
    ttick = 0:30:330;
  endif

  lprops = {"linestyle", get(hax, "gridlinestyle"), ...
            "linewidth", get(hax, "linewidth")};
  ## "fontunits" should be first because it affects "fontsize" property.
  tprops(1:2:12) = {"fontunits", "fontangle", "fontname", "fontsize", ...
                    "fontweight", "interpreter"};
  tprops(2:2:12) = get (hax, tprops(1:2:12));

  ## The number of points used for a circle
  circle_points = 50;
  t = linspace (0, 2*pi, circle_points)';
  x = kron (cos (t), rtick);
  y = kron (sin (t), rtick);

  ## Draw colored disk under axes at Z-depth = -1
  patch (x(:,end), y(:,end), -ones (circle_points, 1),
         get (hax, "color"), "parent", hg);

  ## Plot dotted circles
  line (x(:,1:end-1), y(:,1:end-1), lprops{:}, "parent", hg);

  ## Outer circle is drawn solid
  line (x(:,end), y(:,end), lprops{:}, "linestyle", "-", "parent", hg);

  ## Add radial labels
  [x, y] = pol2cart (0.42 * pi, rtick);
  text (x, y, num2cell (rtick), "verticalalignment", "bottom", tprops{:},
        "parent", hg);

  ## add radial lines
  s = rtick(end) * sin (ttick * pi / 180);
  c = rtick(end) * cos (ttick * pi / 180);
  x = [zeros(1, numel (ttick)); c];
  y = [zeros(1, numel (ttick)); s];
  line (x, y, "linestyle", ":", lprops{:}, "parent", hg);

  ## add angular labels
  tticklabel = num2cell (ttick);
  ## FIXME: This tm factor does not work as fontsize increases
  tm = 1.08;
  text (tm * c, tm * s, tticklabel, "horizontalalignment", "center",
        tprops{:}, "parent", hg);

  lim = 1.1 * rtick(end);
  set (hax, "xlim", [-lim, lim], "ylim", [-lim, lim]);

  ## Put polar grid behind or ahead of plot
  __update_layer__ (hax, [], hg);

endfunction


%!demo
%! clf;
%! theta = linspace (0,2*pi,1000);
%! rho = sin (7*theta);
%! polar (theta, rho);
%! title ('polar() plot');

%!demo
%! clf;
%! theta = linspace (0,2*pi,1000);
%! cplx = theta + i*sin (7*theta);
%! polar (cplx, 'g');
%! title ('polar() plot of complex data');

%!demo
%! clf;
%! theta = linspace (0,2*pi,1000);
%! rho = sin (2*theta).*cos (2*theta);
%! polar (theta, rho, '--r');
%! set (gca, "rtick", 0.1:0.1:0.6, "ttick", 0:20:340);
%! title ('polar() plot with finer grid');

%!demo
%! clf;
%! theta = linspace (0,2*pi,1000);
%! rho = sin (2*theta).*cos (2*theta);
%! polar (theta, rho, '--b');
%! set (gca, "fontsize", 12, "linewidth", 2, "color", [0.8 0.8 0.8]);
%! title ('polar() plot with modified axis appearance');

%!demo
%! clf;
%! theta = linspace (0,8*pi,1000);
%! rho = sin (5/4*theta);
%! polar (theta, rho);
%! set (gca, "rtick", 0.2:0.2:1);
%! title ('polar() plot');