-
Notifications
You must be signed in to change notification settings - Fork 3
/
bubbleplot.m
222 lines (221 loc) · 8.39 KB
/
bubbleplot.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
function [lh, th] = bubbleplot(x, y, z, siz, col, shape, varargin)
% BUBBLEPLOT produces a scatter plot that enables the visualization of upto
% 6-dimensional data. The dimensions used for displaying data include the
% X, Y and Z coordinates, the marker size, color and shape.
%
% USAGE:
% BUBBLEPLOT(x, y, z, siz, col, shape)
% draws a 3D bubble plot. See input parameter description below.
%
% BUBBLEPLOT(x, y, [], siz, col, shape)
% draws a 2D bubble plot.
%
% BUBBLEPLOT(..., textarray)
% enables you to pass in a cell array of strings to annotate each point
% on the plot. By default the strings are displayed as text labels as well
% as stored in the UserData property of the line objects
%
% BUBBLEPLOT(..., textarray, 'ShowText', false)
% will not display the text on screen, but will store it in the user
% data property. This can be useful when creating a custom data tip.
%
% [hLine, hText] = BUBBLEPLOT(...)
% returns a vector of line handles to the points in the plot and
% (if appropriate) a vector of handles to the text objects on the
% screen.
%
% INPUT PARAMETERS:
% The inputs should be vectors of the same length, with the following
% requirements:
% Input Required Default-Value Type
% x, y, z Yes N/A Numerical (Continuous or discrete)
% siz No 8 Numerical (Continuous or discrete)
% col No col = z Numerical or Categorical*
% shape No 'o' Categorical* (upto 13 unique discrete values)
%
% NOTES:
% * "Categorical" variables can either be numeric with discrete values or
% non-numeric data types that support a "UNIQUE" method. Examples of this
% can be a cell array of strings, a nominal array or ordinal array.
%
% * The siz variable is normalized to a marker size range of 3 to 20. To
% specify a custom size range use the optional parameter
% 'markerSizeLimits'. Example: BUBBLEPLOT(..., 'MarkerSizeLimits', [5 32])
%
% * The shape variable can also be a character that represents a marker
% shape to be used for all points
%
% * If col is a categorical variable, ensure it is integer-valued so that
% it is handled correctly. If it is not integer valued, BUBBLEPLOT will
% check to see if the number of unique values is less than 10% of the
% length of the vector and use that to determine if the variable is
% categorical. The colors used to depict categorical data are evenly
% spaced (1 color level per unique category/label). However if col is
% not categorical, its values are simply scaled to different values in
% the colormap
%
% * The default font size used to display the text labels is 8 pt with a
% left alignment. Use the input arguments 'FontSize' and 'Alignment' to
% control these properties.
% Example: BUBBLEPLOT(..., 'FontSize', 6, 'Alignment', 'center')
%
% * You can specify a custom colormap using the Colormap argument. The
% parameter can be a string (eg. 'cool'), a function handle (eg. @jet) or
% an N-by-3 matrix of color RGB levels.
% Example: BUBBLEPLOT(..., 'ColorMap', cmap)
% Copyright 2009-2014 The MathWorks, Inc.
%% Parse input params and defaults
% Check number of input arguments
error(nargchk(2,10,nargin,'struct'));
% Default z
if nargin < 3
z = [];
end
% Default size
if nargin < 4 || isempty(siz)
siz = 8;
end
if nargin < 5 || isempty(col)
col = z;
end
if nargin < 6 || isempty(shape)
shape = 'o';
end
p = inputParser;
p.addOptional('Text',{},@(x)iscellstr(x)||(ischar(x)&&size(x,1)>1)||(~ischar(x)&&length(x)>1));
p.addParamValue('ShowText',true);
p.addParamValue('FontSize',8);
p.addParamValue('Alignment', 'left');
p.addParamValue('MarkerSizeLimits',[3 20]);
p.addParamValue('ColorMap',@cool);
p.parse(varargin{:});
desctext = p.Results.Text;
showText = p.Results.ShowText;
if isempty(desctext), showText = false; end
fontSize = p.Results.FontSize;
alignment = p.Results.Alignment;
colmapfun = p.Results.ColorMap;
markerSizeLimits = p.Results.MarkerSizeLimits;
%% Determine marker colors
if ischar(colmapfun)
colmapfun = str2func(colmapfun);
elseif isnumeric(colmapfun)
colmapfun = @(x)colmapfun(1:min(x,end),:);
end
if isempty(col)
col = zeros(size(x));
end
[uniqueCols, gar, colInd] = unique(col);
if isinteger(col) || isa(col,'categorical') || iscell(col) || length(uniqueCols)<=.1*length(col) || all(round(col)==col) % Is col categorical
% Generate a colormap with one level per unique entry in col
colmap = colmapfun(length(uniqueCols));
else
% Scale the color values to span the colormap
colmap = colmapfun(256);
mx = max(col);
n = min(col);
if mx == n, mx = n + 1; end
colInd = (col-n)/(mx-n)*(size(colmap,1)-1)+1;
end
try
color = colmap(round(colInd),:);
catch %#ok<CTCH>
error('The custom colormap must have at least %d levels', max(colInd));
end
%% Determine marker shape
if ischar(shape)
markertype = repmat(shape(1),size(x));
else
markerseq = 'osd^><vph.*+x';
[uniqueShapes, gar, shapeInd] = unique(shape);
if length(uniqueShapes)>length(markerseq)
error('BubblePlot can only support 13 unique shapes');
end
markertype = markerseq(shapeInd);
end
%% Determine marker size
if isscalar(siz)
siz = repmat(siz, size(x));
markersize = siz;
else % Map the siz variable to a markersize between a minimum and maximum
minsize = markerSizeLimits(1);
maxsize = markerSizeLimits(2);
markersize = (siz - min(siz))/(max(siz)-min(siz))*(maxsize - minsize)+minsize;
end
%% Clean up data - handle NaNs
markersize(isnan(markersize)) = .01; % These will not be drawn as regular markers, just pixel points
%isnan(x) | isnan(y) | isnan(z) | isnan(col) |
%% Plot data
% Create structure to store original data in every graphics object (for
% subsequent retrieval, eg: with data tip)
pointData = struct('x',num2cell(x),'y',num2cell(y),'siz',num2cell(siz),'col',num2cell(col),...
'shape',num2cell(shape));
if nargin > 6 && ~isempty(desctext)
if ~iscellstr(desctext)
desctext = cellstr(desctext);
end
[pointData.text] = desctext{:};
end
if isempty(z)
plotfun = @plot;
%plotfun = @patch;
%zarg = {color(1,:)};
zarg = {};
else
plotfun = @plot3;
zarg = {z(1)};
zdata = num2cell(z);
[pointData.z] = zdata{:};
end
lh = zeros(1,length(x)); % Line Handles
lh(1) = customPlot(plotfun, pointData(1), color(1,:), markersize(1), markertype(1), x(1), y(1), zarg{:});
for i = 2:length(lh)
if isempty(z), zarg = {}; else zarg = {z(i)}; end
%if isempty(z), zarg = {color(i,:)}; else zarg = {z(i)}; end
lh(i) = customPlot(@line, pointData(i), color(i,:), markersize(i), markertype(i), x(i), y(i), zarg{:});
%lh(i) = customPlot(@patch, pointData(i), color(i,:), markersize(i), markertype(i), x(i), y(i), zarg{:});
end
if showText
hAxes = get(lh(1),'Parent');
offset = diff(get(hAxes,'Ylim'))*.01;
if isempty(z)
z = zeros(size(x));
end
th = text(x, y-offset, z, desctext, 'Fontsize', fontSize, 'HorizontalAlignment', alignment);
lims = get(hAxes,{'XLim','YLim','ZLim'});
lims = vertcat(lims{:});
factor = fontSize.*diff(lims,[],2);
addlistener(hAxes,{'XLim','YLim'},'PostSet',@(obj,evdata)resizeText(hAxes, th, y, factor));
%addlistener(get(hAxes,'Parent'),'Resize',@(obj,evdata)resizeText(hAxes, th));
else
th = [];
end
function lh = customPlot(funh, pointData, c, siz, markertype, varargin)
lh = funh(varargin{:});
set(lh, 'Marker', markertype,...
'LineStyle', 'none', 'Color', c, ...
'MarkerFaceColor', c, ...
'MarkerEdgeColor', [0 0 0], 'MarkerSize', siz,...
'UserData', struct('Point',pointData));
% lh = patch('XData',x(i),'YData', y(i), 'ZData', z(i), 'Marker', 'o',...
% 'LineStyle', 'none', 'CData', color, 'MarkerFaceColor', c, ...
% 'MarkerEdgeColor', [0 0 0], 'MarkerSize', siz2(i), 'FaceAlpha', .4, 'EdgeAlpha', .2, ...
% 'UserData', data);
function resizeText(hAxes, hText, y, factor) %#ok<*INUSD>
lims = get(hAxes,{'XLim','YLim','ZLim'});
lims = vertcat(lims{:});
% Uncomment following to update fontsize
% newfs = min([factor(1:2)./diff(lims(1:2,:),[],2) ; 24]);
% set(hText,'FontSize',newfs);
% Update position
offset = diff(get(hAxes,'Ylim'))*.01;
p = get(hText,'Position');
p = vertcat(p{:});
outofbounds = any(bsxfun(@gt,p,lims(:,2)') | bsxfun(@lt,p,lims(:,1)'), 2);
set(hText(outofbounds),'Visible','off');
set(hText(~outofbounds),'Visible','on');
% Adjust offsets
p(:,2) = y - offset;
for i = 1:length(p)
set(hText(i),'Position',p(i,:));
end