Code covered by the BSD License  

Highlights from
Color coded 3D scatterplot

image thumbnail

Color coded 3D scatterplot

by

 

05 Jan 2006 (Updated )

Produces a 3D scatterplot of XYZ data with varying colors

plot3k(L,varargin)
function [f,g,h] = plot3k(L,varargin)
% PLOT3K  Color coded 3D scatterplot
%   [f,g,h] = plot3k(L,varargin)
% Input
%   L    An (x,y,z) vector of data points (n x 3) or
%        an (n x m) matrix with m > 3 or
%        a 3 element cell array containing x, y and z vectors or matrices.
%        For cell array input x and y may be matrices the same size as z
%        or vectors of row and column locations on a grid.  If L is an n x m
%        matrix, an xy grid is created using meshgrid(1:n,1:m).
%
%   A 3D scatterplot of the data points in L is produced using the magnitude
%   of z to color the points according to their distance from the xy plane.
%   Plot3 is called once for each group of points that map to the same color.
%   The color mapping, color range, point markers and labels can be changed
%   with optional property,value pair arguments.
%
%   varargin       'property','value' pairs that modify plot characteristics.
%      Property names must be specified as quoted strings. Property names and
%      their values are not case sensitive. For each property the default value
%      is given below.  All properties are optional. 
%
%   'ColorData'    Array of color values (default = 3rd column of L)
%      The data points are colored according to the colordata array.  The
%      number of values must equal the length of L.  A row vector, column
%      vector or matrix can be specified.
%
%   'ColorRange'   Color mapping limits  (default = [min(L(:,3) max(L,:3)])
%      A two element vector specifying the values that map to the first and
%      last colors.  This is useful for generating a series of plots with
%      identical coloring.  The colormap (but not the colorbar) is flipped
%      upside down if 'ColorRange' is given as [max min] instead of [min max]. 
%
%   'Marker'       Cell array containing point marker character and size
%      Any marker from the standard plot set '+o*.xsd^v><ph' and its size.
%      The default is {'.' 6}.
%
%   'CBLabels'     Number of colorbar labels (default = 11)
%
%   'CBFormat'     Format string for printing colorbar labels
%      Any sprintf format string with a numeric conversion code.
%      The default is '%5.3g'.
%
%   'Labels'       A cell array of five label strings (default = {})
%      These strings are used as the plot title, x axis label, y axis label,
%      z axis label and colorbar title.  To skip a string specifiy ''.
%
%   'PlotProps'    A cell array of 'property','value' pairs
%      Plotprops are applied after the plot is drawn.
%      The default value is an empty cell array.
%
% Output
%   f,g,h         Handles for the figure, axes and colorbar
%
% Example
%   figure('color','white');
%   [x,y,z] = peaks(101);
%   c = gradient(z);
%   k = hypot(x,y)<3;
%   plot3k({x(k) y(k) z(k)},...
%      'ColorData',c(k),'ColorRange',[-0.5 0.5],'Marker',{'o',2},...
%      'Labels',{'Peaks','Radius','','Intensity','Lux'},...
%      'PlotProps',{'FontSize',12});
%
% Ken Garrard, North Carolina State University, 2005 - 2013
% Based on plot3c by Uli Theune, University of Alberta
%
% 2008.11.07  Updated color range and marker arguments
% 2010.12.14  Updated input L to allow x,y vectors and z matrix
% 2011.01.04  Include axes and colorbar handles as output arguments
% 2011.10.14  Optional arguments are now property,value pairs
% 2012.03.08  The NextPlot property treatment has been updated
% 2013.08.17  Excludes points with NaN color values from the plot

% -- Help

% Plot3k was called without arguments, plot an example in a new figure
% and display help text
if nargin < 1
   figure('color','white');
   [x,y,z] = peaks(101);
   c = gradient(z);
   k = hypot(x,y)<3;
   plot3k({x(k) y(k) z(k)},...
      'ColorData',c(k),'ColorRange',[-0.5 0.5],'Marker',{'o',2},...
      'Labels',{'Peaks','Radius','','Intensity','Lux'},...
      'PlotProps',{'FontSize',12});
   error(['No input arguments given\n'...
          'Please consult the help text and the example plot\n'...
          '--------\n%s'],help(mfilename));
end

%-- Parse and validate input arguments

% Convert singleton cell array to its contents
while iscell(L) && length(L) == 1, L = L{1}; end

% Check for cell array location data
if iscell(L)
   
   % cell array input must have three elements
   if length(L) ~= 3
      error('Location cell array must contain three elements');
   end

   % convert cell array elements to column vectors
   x = L{1}(:);   y = L{2}(:);   z = L{3}(:);

   % make a grid from col,row vectors
   if isequal(numel(x)*numel(y),numel(z))
      [x,y] = meshgrid(x,y);

   % equal length x, y and z vectors ?
   elseif ~isequal(length(x),length(y),length(z))
      error('Location cell array elements must be same length');
   end

   % convert to n x 3 matrix
   L = [x(:) y(:) z(:)];

% Location data may be a 2d matrix
elseif ndims(L) == 2 && size(L,2) > 3 %#ok<ISMAT>

   % use pixel values for x,y
   [x,y] = meshgrid(1:size(L,2),1:size(L,1));

   % convert to n x 3 matrix
   L = [x(:) y(:) L(:)];

% Otherwise location data must have 3 columns
elseif size(L,2) ~= 3
   error('Location vector must have 3 columns');
end

% Set up property structure with default values
p.colordata   = [];              % color by magnitude of z
p.colorrange  = [];              % color range = full range of z
p.marker      = {'.' 6};         % point marker
p.cblabels    = 11;              % number of colorbar labels
p.cbformat    = '%5.3g';         % colorbar label format string
p.labels      = {};              % no labels
p.plotprops   = {};              % no additional plot properties

% Parse property value pairs, replace defaults with values specified by caller
try
   p = structrecon(pv2struct(varargin{:}),p);
catch ERR
   error('Error parsing varargin list\n??? %s',ERR.message);
end

% Color vector must be same length as x,y,z location data or empty
p.colordata = p.colordata(:);
if ~isempty(p.colordata) && (size(L,1) ~= length(p.colordata))
   error('Location vector and color data vector must be the same length');
end

% Validate marker character
if ~iscell(p.marker), mark_ch = p.marker;  mark_sz = 6;
else
    mark_ch = p.marker{1};
    if length(p.marker)>1, mark_sz = p.marker{2};
    else                   mark_sz = 6;
    end
end
marker_str = '+o*.xsd^v><ph';
if (length(mark_ch) ~= 1) || isempty(strfind(marker_str,mark_ch))
   error('Invalid marker character, select one of ''%s''', marker_str);
end
mark_sz = max(mark_sz,0.5);

% Validate label strings cell array structure
if ~iscell(p.labels)
   error('labels argument should be a cell array');
end


% -- Setup colormap, sort data by color

% Find color limits and range
if isempty(p.colordata)                  % user specified colordata ?
   p.colordata = L(:,3);                 % no, color by magnitude of z
end
if isempty(p.colorrange)                 % user specified colorrange ?
   min_c = min(p.colordata);             % no, use colordata range
   max_c = max(p.colordata);
else                                     % user specified color range
   min_c = p.colorrange(1);
   max_c = p.colorrange(2);
end
range_c = max_c - min_c;

% Get current colormap
cmap = colormap;
if range_c < 0, cmap = flipud(cmap); end
clen = length(cmap);

% Calculate color value for each point
L(:,4) = ...
   min(max(round((p.colordata-min(min_c,max_c))*(clen-1)/abs(range_c)),1),clen);

% Don't plot points with NaN color values
L(isnan(p.colordata),3) = NaN;

% Sort by color value
L = sortrows(L,4);


%-- Plot data points in groups by color value

% Build index vector of color transitions (last point for each color)
dLix = [find(diff(L(:,4))>0); size(L,1)];

nxtplot = get(gca,'NextPlot');     % save 'NextPlot' property value
s = 1;                             % index of 1st point in a  color group
for k = 1:length(dLix)             % loop over each non-empty color group
   plot3(L(s:dLix(k),1), ...       % call plot3 once for each color group
         L(s:dLix(k),2), ...
         L(s:dLix(k),3), ...
         mark_ch,           ...
         'MarkerSize',mark_sz, ...
         'MarkerEdgeColor',cmap(L(s,4),:), ... % same marker color from cmap
         'MarkerFaceColor',cmap(L(s,4),:), ... % for all points in group
         'Tag','plot3k');                      % tag each group
   s = dLix(k)+1;                  % next group starts at next point
   if k == 1, hold on; end         % add next group to same axes
end
set(gca,'NextPlot',nxtplot);       % restore 'NextPlot' property


% -- Add title and axes labels

fontargs = {'FontName','Arial','FontSize',10,'FontWeight','bold'};
s = repmat({''},5-length(p.labels),1);
strlab = {p.labels{:} s{:}}; %#ok<CCAT>
title (strlab{1},fontargs{:});
xlabel(strlab{2},fontargs{:});
ylabel(strlab{3},fontargs{:});
zlabel(strlab{4},fontargs{:});

% Set plot characteristics
view([-32,32]);
grid on;
set(gca,fontargs{:});

% Apply user specified plot properties
if ~isempty(p.plotprops), set(gca,p.plotprops{:}); end


% -- Format the colorbar

h    = colorbar;
nlab = abs(p.cblabels);                  % number of labels must be positive
set(h,'YLim',[1 clen]);                  % set colorbar limits
set(h,'YTick',linspace(1,clen,nlab));    % set tick mark locations

% Create colorbar tick labels based on color vector data values
tick_vals = linspace(min_c,max_c,nlab);

% Create cell array of color bar tick label strings
labels = cell(1,nlab);
for i = 1:nlab
   labels{i} = sprintf(p.cbformat,tick_vals(i));
end

% Set tick label strings
set(h,'YTickLabel',labels,fontargs{:});
title(h,strlab{5});

if nargout > 0, f = gcf; end             % return figure handle
if nargout > 1, g = gca; end             % return axes   handle
end

% Structure reconciliation with a template
function T = structrecon(S,D)

% Check arguments, must have two structures
if ~(isstruct(S) && isstruct(D))
   error('input arguments must be structures');
end
   
T     = D;             % copy the template
fname = fields(T);     % make a list of field names

% Loop over all fields in the template, copy matching values from S
for k = 1:length(fname)
   % Process matching field names in S
   if isfield(S,fname{k})
      % Is this a substructure ?
      if isstruct(T.(fname{k})) && isstruct(S.(fname{k}))
         % Recursively process the substructure
         T.(fname{k}) = structrecon(S.(fname{k}),T.(fname{k}));
      % Not a substructure, copy field value from S
      else T.(fname{k}) = S.(fname{k});
      end
   end
end
end

% Convert argument pairs to a structure
function S = pv2struct(varargin)

% No inputs, return empty structure
if isempty(varargin), S = struct(); return; end

% Need pairs of inputs
if mod(length(varargin),2)==1
   error('number of arguments must be even');
end

% Odd elements of varargin are fields, even ones are values
% Store all field names in lower case
for k = 1:2:length(varargin)
   S.(lower(varargin{k})) = varargin{k+1};
end
end

Contact us