No BSD License  

Highlights from
pie2

image thumbnail
from pie2 by Richard de Garis
Modification of the Matlab PIE function to account for label overlaps

pie(varargin)
function hh = pie(varargin)
%PIE    Pie chart.
%   PIE(X) draws a pie plot of the data in the vector X.  The values in X
%   are normalized via X/SUM(X) to determine the area of each slice of pie.
%   If SUM(X) <= 1.0, the values in X directly specify the area of the pie
%   slices.  Only a partial pie will be drawn if SUM(X) < 1.
%
%   PIE(X,EXPLODE) is used to specify slices that should be pulled out from
%   the pie.  The vector EXPLODE must be the same size as X. The slices
%   where EXPLODE is non-zero will be pulled out.
%
%   PIE(...,LABELS) is used to label each pie slice with cell array LABELS.
%   LABELS must be the same size as X and can only contain strings.
%
%   PIE(AX,...) plots into AX instead of GCA.
%
%   H = PIE(...) returns a vector containing patch and text handles.
%
%   Example
%      pie([2 4 3 5],{'North','South','East','West'})
%
%   See also PIE3.

%   Clay M. Thompson 3-3-94
%   Copyright 1984-2005 The MathWorks, Inc.
%   $Revision: 1.16.4.8 $  $Date: 2005/10/28 15:54:38 $

% Parse possible Axes input
[cax,args,nargs] = axescheck(varargin{:});
error(nargchk(1,3,nargs,'struct'));

x = args{1}(:); % Make sure it is a vector
args = args(2:end);

nonpositive = (x <= 0);
if all(nonpositive)
    error('MATLAB:pie:NoPositiveData',...
        'Must have positive data in the pie chart.');
end
if any(nonpositive)
  warning('MATLAB:pie:NonPositiveData',...
          'Ignoring non-positive data in pie chart.');
  x(nonpositive) = [];
end

% RICHARD HACK #1: The Mathworks' PIE function only scales pie slices to sum to
% one if the sum of the values is greater than 1+sqrt(eps).  In this
% version,scaling is performed regardless.
x = x/sum(x);
% END RICHARD HACK #1

% Look for labels
if nargs>1 && iscell(args{end})
  txtlabels = args{end};
  if any(nonpositive)
    txtlabels(nonpositive) = [];
  end
  args(end) = [];
else
  for i=1:length(x)
    if x(i)<.01,
      txtlabels{i} = '< 1%';
    else
      txtlabels{i} = sprintf('%d%%',round(x(i)*100));
    end
  end
end

% Look for explode
if isempty(args),
   explode = zeros(size(x)); 
else
   explode = args{1};
   if any(nonpositive)
     explode(nonpositive) = [];
   end
end

explode = explode(:); % Make sure it is a vector

if length(txtlabels)~=0 && length(x)~=length(txtlabels),
  error(id('StringLengthMismatch'),'Cell array of strings must be the same length as X.');
end

if length(x) ~= length(explode),
  error(id('ExploreLengthMismatch'),'X and EXPLODE must be the same length.');
end

cax = newplot(cax);
next = lower(get(cax,'NextPlot'));
hold_state = ishold(cax);

theta0 = pi/2;
maxpts = 100;
inside = 0;

h       = [];
midArcX = zeros(length(x),1,'double');
midArcY = zeros(length(x),1,'double');

for i=1:length(x)
  n = max(1,ceil(maxpts*x(i)));
  r = [0;ones(n+1,1);0];
  theta = theta0 + [0;x(i)*(0:n)'/n;0]*2*pi;
  if inside,
    [xtext,ytext] = pol2cart(theta0 + x(i)*pi,.5);
  else
    [xtext,ytext] = pol2cart(theta0 + x(i)*pi,1.2);
  end
  [xx,yy] = pol2cart(theta,r);
  
  % store XY co-ordinates of the mid-point on the circumference of the pie
  % slice arc for later use in drawing a 'leader' line.
  [midArcX(i), midArcY(i)] = pol2cart(theta0 + x(i)*pi,1);
  
  if explode(i),
    [xexplode,yexplode] = pol2cart(theta0 + x(i)*pi,.1);
    xtext = xtext + xexplode;
    ytext = ytext + yexplode;
    xx = xx + xexplode;
    yy = yy + yexplode;
  end
  theta0 = max(theta);
  
  % RICHARD HACK #2: The Mathworks' PIE function sets the horizontal alignment
  % of text labels to 'center'.  In this version, labels on the left hand side
  % of the pie chart are 'right' aligned, while labels on the right hand side
  % are 'left' aligned.  This counteracts the horizontal overlapping that occurs
  % when labels are clustered at the top and bottom ends of the pie.
  % A future enhancement would be to selectively invoke 'left' and 'right' 
  % alignments only where horizontal overlaps actually exist.
  
  if xtext < 0
      horizAlign = 'right';
  else
      horizAlign = 'left';
  end
   
  h = [h,patch('XData',xx,'YData',yy,'CData',i*ones(size(xx)), ...
             'FaceColor','Flat','parent',cax), ...
       text(xtext,ytext,txtlabels{i},...
            'HorizontalAlignment',horizAlign,'parent',cax)];
        
  % END RICHARD HACK #2
end

% RICHARD HACK #3: with all labels in place, detect vertical overlaps and shift
% the overlapping label vertically towards the next label by the overlap amount.
% The process is as follows:
% 1) get the text positions and extents - positions are used to move labels,
% while extents are used to detect overlaps.

% 2) starting at the top and moving anti-clockwise around the pie, consider
% labels in pairs; if each pair is on the same side (left or right) then
% continue, else skip to the next pair.

% 3) calculate the absolute vertical distance between the y-extents - this is
% the delta-y.
%
% 4) if the y-extent of the first label is greater than the y-extent of the 
% second label, then the labels are on the left hand side of the pie, 
% otherwise they're on the right hand side.

% 5) if on the left side, then if the delta-y is less than the height-extent of
% the second label, then there is an overlap and the second label should be 
% moved down by the overlap amount, otherwise do nothing and move on.

% 6) if the labels are on the right hand side of the pie - item 4), above, then
% if the delta-y is less than the height-extent of the first label, then
% there is an overlap and the second label should be moved up by the overlap
% amount, otherwise do nothing and move on.

% 7) where an overlap occurs, and once a label has been shifted, draw a
% leader line from the pie slice to the text label for added clarity.

textHandle   = findobj(h,'Type','text');
textPosition = get(textHandle, 'Position');
textOverlap  = 0;
POSN_SCALAR  = 0.95; % scales X coordinate of the line at the label end
  
for i = 1:size(textHandle)-1  % going anti-clockwise around the pie from the top
    % refresh textExtent on each iteration to reflect label movements
    textExtent = get(textHandle, 'Extent');  
    deltaY     = abs(textExtent{i}(2) - textExtent{i+1}(2));
    
    if sign(textExtent{i}(1)) == sign(textExtent{i+1}(1))  % same side of the pie
        if textExtent{i}(2) > textExtent{i+1}(2)  % left side of pie
            textOverlap = textExtent{i+1}(4) - deltaY;
            if textOverlap > 0
                % move text label
                textPosition{i+1}(2) = textPosition{i+1}(2) - textOverlap;
                set(textHandle(i+1),'Position',textPosition{i+1});
                % draw leader line to each label in the pair
                line([midArcX(i) textPosition{i}(1)*POSN_SCALAR],...
                     [midArcY(i) textPosition{i}(2)],...
                     'color', 'k', 'clipping', 'off');
                line([midArcX(i+1) textPosition{i+1}(1)*POSN_SCALAR],...
                     [midArcY(i+1) textPosition{i+1}(2)],...
                     'color', 'k', 'clipping', 'off');
            end
        else    % right side of the pie
            textOverlap = textExtent{i}(4) - deltaY;
            if textOverlap > 0
                % move text label
                textPosition{i+1}(2) = textPosition{i+1}(2) + textOverlap;
                set(textHandle(i+1),'Position',textPosition{i+1});
                % draw leader line to each label in the pair
                line([midArcX(i) textPosition{i}(1)*POSN_SCALAR],...
                     [midArcY(i) textPosition{i}(2)],...
                     'color', 'k', 'clipping', 'off');
                line([midArcX(i+1) textPosition{i+1}(1)*POSN_SCALAR],...
                     [midArcY(i+1) textPosition{i+1}(2)],...
                     'color', 'k', 'clipping', 'off');
            end
        end
    end
end
  
% END RICHARD HACK #3

if ~hold_state, 
  view(cax,2); set(cax,'NextPlot',next); 
  axis(cax,'equal','off',[-1.2 1.2 -1.2 1.2])
end

if nargout>0, hh = h; end

% RICHARD HACK #4: register handles with m-code generator.  In The Mathworks'
% PIE function a call is made to a private function MCODEREGISTER.  The m-file
% for this function requests that MAKECODE is called instead, which is what this
% version does here.

if ~isempty(h) && ~isdeployed
  makemcode('RegisterHandle',h,'IgnoreHandle',h(1),'FunctionName','pie');
end

% END RICHARD HACK #4

function str=id(str)
str = ['MATLAB:pie:' str];

Contact us at files@mathworks.com