-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpolarplot3d.m
800 lines (689 loc) · 30.1 KB
/
polarplot3d.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
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
function [Xi,Yi,Zi] = polarplot3d(Zp,varargin)
% POLARPLOT3D Plot a 3D surface from polar coordinate data
% [Xi,Yi,Zi] = polarplot3d(Zp,varargin)
%
% Input
% Zp A two dimensional matrix of input magnitudes or intensities.
% Each column of Zp contains information along a single meridian. Each row
% of Zp contains information at a single radius. The direction sense of the
% rows and columns is determined by the relative order of the angular and
% radial range vectors. By default Zp is increasing in radius down each
% column and increasing in angle along each row in the counter-clockwise
% direction. The default plot is a full 360 degree surface plot with unit
% radius.
%
% 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. They may be given in any
% order.
%
% 'PlotType' 'surfn' surface plot, no mesh (default)
% 'surfcn' surface plot with contours, no mesh
% 'surfc' surface plot with contours
% 'surf' surface plot
% 'mesh' mesh plot
% 'meshc' mesh plot with contours
% 'contour' 2D contour plot
% Use 'ContourLines' to set the number of contours.
% 'wire' wireframe polar grid plot only, no surface plot
% A wireframe polar grid is plotted without a surface.
% 'off' no surface plot
% The data is interpolated to a new grid according to
% the 'MeshScale' property and transformed to Cartesian
% coordinates.
%
% 'AngularRange' scalar or vector, radians (default = [0 2*pi])
% 'RadialRange' scalar or vector (default = [0 1])
% If a scalar is given for either range it is used as the maximum value and
% zero is used for the minimum. If a two element vector is given, columns
% (or rows) are evenly spaced between these values. Otherwise the number
% of elements must match the size of the corresponding dimension of Zp and
% specifies the location of each column (or row). If the vector values are
% decreasing the corresponding dimension of Zp is reversed.
%
% 'ColorData' Matrix of color values, size must be equal to size(Zp)
% The default coloring is according to the magnitude of Zp. For example
% specifying gradient(Zp) colors the plot according to slope in the radial
% direction. Similarly gradient(Zp.').' colors the surface according to
% slope in the azimuthal direction.
%
% 'CartOrigin' Cartesian axis origin, 3 element vector (default = [0 0 0])
% The center of the polar plot is translated to this location.
%
% 'MeshScale' Mesh scale factors, 2 element vector (default = [1 1])
% The data is interpolated to a new mesh size. Values > 1 increase mesh
% element size and values < 1 decrease mesh element size.
%
% 'TickSpacing' Spacing of polar tick marks in degrees (default = 10)
% Every other tick mark is labeled. To supress tick marks specify zero,
% an empty vector or an increment value greater than 180.
%
% 'PolarGrid' Polar grid density (2 element cell array) (default = {10 8})
% Number of grid sections in the radial and azimuthal directions. A value
% of 1 eliminates a grid direction from the plot. If a vector is specified
% for a direction, gridlines are drawn at the specified locations.
%
% 'GridScale' Smoothness of contoured grid lines (default = [40 40])
% Larger values make the grid lines smoother.
%
% 'GridStyle' Style of polar grid lines (default = ':' dotted line)
% Plotting style for the radial and azimuthal grid lines. Any format
% supported by the plot function can be used: '-' solid, ':' dotted,
% '-.' dashdot or '--' dashed.
%
% 'ContourLines' Scalar (number of contours) or a vector (contour locations)
% Either the number of contours or the location of contour lines is specified.
% The default is auto selection by the contour function.
%
% 'AxisLocation' 'surf' polar axis along edge of surface (default)
% 'min' polar axis at minimum Zp for largest radius
% 'max' polar axis at maximum Zp for largest radius
% 'mean' polar axis at mean Zp for largest radius
% 'top' polar axis at top of plot box
% 'bottom' polar axis at bottom of plot box
% value polar axis is drawn at specified height
% 'off' no polar axis
%
% 'RadLabels' Number of radial labels (default = 0)
% The inner and outer most radii are not labeled. Labels are equally spaced
% within the radial range of the data.
%
% 'RadLabelLocation' Radial label location (2 element cell array) (default = {0 'max'})
% The first elemenent is the azimuthal location of the radial labels in
% degrees. The second element is the sagittal location of the labels. The
% following values are allowed. If a value is given it can be a scalar or
% a vector the same length as the value of the 'RadLabels' property.
%
% 'surf' above the surface at each theta,R
% 'min' at minimum value of Zp
% 'max' at maximum value of Zp (default)
% 'mean' at mean value of Zp
% 'top' at the top of the plot box
% 'bottom' at the bottom of the plot box
% value at the specified height(s)
% 'off' no radial labels
%
% 'PolarDirection' 'ccw' 0 degs along +x axis, angles increase ccw (default)
% 'cw' 0 degs along +y axis, angles increase cw
% This makes a compass-style polar grid.
%
% 'InterpMethod' 'cubic' bicubic interpolation on Zp (default)
% 'linear' bilinear interpolation on Zp
% 'spline' spline interpolation on Zp
% 'nearest' nearest neighbor interpolation on Zp
%
% 'PolarAxisColor' color of the polar axis (default = 'black')
% 'GridColor' color of polar grid lines (default = 'black')
% 'TickColor' color of polar tick marks (default = 'black')
% 'TickLabelColor' color of polar tick labels (default = 'black')
% 'RadLabelColor' color of radial labels (default = 'black')
%
% Additional 'property',value pairs are applied to the current
% axis using the set() command after the polar plot is drawn.
%
% Output
% Xi,Yi,Zi Cartesian locations corresponding to polar coordinates (T,R,Zp)
% T and R are created from AngularRange and RadialRange arguments
% using meshgrid and converted to Cartesian coordinates with
% pol2cart. Xi,Yi,Zi are square matrices with size equal to the
% dimensions of Zp after interpolation. Matrix sizes are reduced
% or enlarged by the MeshScale property.
%
% Notes Zp is the only required input argument
% If no input arguments are given an example plot is produced
% and this help text is displayed in the command window.
%
% ----
% Example
% [t,r] = meshgrid(linspace(0,2*pi,361),linspace(-4,4,101));
% [x,y] = pol2cart(t,r);
% P = peaks(x,y); % peaks function on a polar grid
%
% % draw 3d polar plot
% figure('Color','white','NumberTitle','off','Name','PolarPlot3d v4.3');
% polarplot3d(P,'PlotType','surfn','PolarGrid',{4 24},'TickSpacing',8,...
% 'AngularRange',[30 270]*pi/180,'RadialRange',[.8 4],...
% 'RadLabels',3,'RadLabelLocation',{180 'max'},'RadLabelColor','red');
%
% % set plot attributes
% set(gca,'DataAspectRatio',[1 1 10],'View',[-12,38],...
% 'Xlim',[-4.5 4.5],'Xtick',[-4 -2 0 2 4],...
% 'Ylim',[-4.5 4.5],'Ytick',[-4 -2 0 2 4]);
% title('polarplot3d example');
%
% ----
% Versions
% 1 Original function based on POLAR3D by J De Freitas
% 2 Changed argument method to 'property',value pairs using PARSE_PV_PAIRS by J. D'Errico
% 2.1 Added 'ColorData' property
% 2.2 Updated contour plot implementation for meshc, surfc and surfcn plot types
% 2.3 Added radial and azimuthal mesh scale factors
% 2.4 Added 'CartOrigin' property
% 2.5 Added 'PolarAxisColor', 'GridColor', 'TickColor' and 'TickLabelColor' properties
% 2.6 Added 'PolarDirection' and 'GridScale' properties
% 3 Removed PARSE_PV_PAIRS dependency
% 4 Support for non-uniform grid spacing. Removed redundant 'MeshL' plot type
% 4.1 Replaced optional 'PlotProps' cell argument with 'property',value list
% 4.2 Added 'RadLabels', 'RadLabelLocation' and 'RadLabelColor' properties
% -- Help
% Polarplot3d was called without arguments
% Draw an example in a new figure and display help text
if nargin < 1
[t,r] = meshgrid(linspace(0,2*pi,361),linspace(-4,4,101));
[x,y] = pol2cart(t,r);
P = peaks(x,y); % peaks function on a polar grid
% draw 3d polar plot
figure('Color','white','NumberTitle','off','Name','PolarPlot3d v4.3');
polarplot3d(P,'PlotType','surfn','PolarGrid',{4 24},'TickSpacing',8,...
'AngularRange',[30 270]*pi/180,'RadialRange',[.8 4],...
'RadLabels',3,'RadLabelLocation',{180 'max'},'RadLabelColor','red');
% set plot attributes
set(gca,'DataAspectRatio',[1 1 10],'View',[-12,38],...
'Xlim',[-4.5 4.5],'Xtick',[-4 -2 0 2 4],...
'Ylim',[-4.5 4.5],'Ytick',[-4 -2 0 2 4]);
title('polarplot3d example');
% display help text
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
% Allowed argument string for property values
plst = {'mesh','meshc','wire',...
'surf','surfc','surfn','surfcn','contour','off'};
alst = {'off','const','min','max','mean','surf','top','bottom'};
rlst = {'off','const','min','max','mean','surf','top','bottom'};
mlst = {'cubic','linear','spline','nearest'};
dlst = {'ccw','cw'};
glst = {'-',':','-.','--'};
% Set up property structure with default values
p.angularrange = [0 2*pi]; % angular range
p.radialrange = [0 1]; % radial range
p.plottype = 'surfn'; % surface plot, no rectangular grid
p.meshscale = [1 1]; % no mesh scaling
p.polargrid = {10 8}; % number of radial and azimuthal sections
p.gridscale = [40 40]; % 40x scaling for smooth grid interpolation
p.cartorigin = [0 0 0]; % Cartesian origin
p.tickspacing = 10; % ten degree tick mark spacing
p.radlabels = 0; % number of radial grid labels
p.axislocation = ''; % polar axis location
p.radlabellocation = ''; % radial label location
p.polardirection = 'ccw'; % default polar axis direction
p.interpmethod = 'cubic'; % bicubic interpolation
p.colordata = []; % default coloring according to Zp values
p.contourlines = ''; % default contour specification
p.gridcolor = 'black'; % default overlay grid line color
p.gridstyle = ''; % style of grid lines: '-' ':' '-.' '--'
p.polaraxiscolor = 'black'; % default polar axis color
p.tickcolor = 'black'; % default polar tick color
p.ticklabelcolor = 'black'; % default polar tick label color
p.radlabelcolor = 'black'; % default radial label color
p.plotprops = {}; % no additional plot properties
% Parse property value pairs, replace defaults with values specified by caller
try
[p,leftovers] = structrecon(pv2struct(varargin{:}),p);
p.plotprops = struct2pv(leftovers);
catch ERR
error('Error parsing varargin list\n??? %s',ERR.message);
end
% Check input data size
[Zrows,Zcols] = size(Zp);
if (Zrows < 5) || (Zcols < 5)
error('Input matrix size must be greater than 4 x 4');
end
% Check plot type specification
if ~ischar(p.plottype) || isempty(matchstr2lst(lower(p.plottype),plst))
error('Invalid ''PlotType'' property value');
end
p.plottype = lower(p.plottype);
% Choose default polar axis location
if isequal(p.axislocation,'')
if ~isempty(matchstr2lst(p.plottype,{'meshc','surfc','surfcn'}))
p.axislocation = 'bottom'; % plot box bottom for contour plots
else p.axislocation = 'surf'; % along perimeter of surface otherwise
end
end
% User specified polar axis location as a numeric value
if isnumeric(p.axislocation)
[r,c] = size(p.axislocation);
if (((r ~= 1) || (c ~= 1)) || ~isnumeric(p.axislocation))
error('''AxisLocation'' property value must be scalar, positive and real');
end
polax = p.axislocation;
p.axislocation = 'const';
end
% Check polar axis location specification
if ~ischar(p.axislocation) || isempty(matchstr2lst(lower(p.axislocation),alst))
error('Invalid ''AxisLocation'' property value');
end
p.axislocation = lower(p.axislocation);
% Check polar axis direction specification
if ~ischar(p.polardirection) || isempty(matchstr2lst(lower(p.polardirection),dlst))
error('Invalid ''PolarDirection'' property value');
end
p.polardirection = lower(p.polardirection);
% Check grid scaling specification
p.gridscale = p.gridscale(:)';
if ~isnumeric(p.gridscale) || any(p.gridscale <= 0)
error('Non-numeric or non-positive grid scale parameter');
end
if length(p.gridscale) ~= 2
error('''GridScale'' property must be a two element numeric vector');
end
% Check angular range vector
p.angularrange = p.angularrange(:);
if ~isnumeric(p.angularrange)
error('''AngularRange'' property value must be numeric');
end
if isscalar(p.angularrange), p.angularrange = [0; p.angularrange]; end
if length(p.angularrange) == 2
p.angularrange = linspace(p.angularrange(1),p.angularrange(2),Zcols).';
end
if ~ismono(p.angularrange)
error('''AngularRange'' vector must be monotonic');
end
if length(p.angularrange) ~= Zcols
error('''AngularRange'' size must match number of columns in input matrix');
end
% Check radial range vector
p.radialrange = p.radialrange(:);
if ~isnumeric(p.radialrange)
error('''RadialRange'' property value must be numeric');
end
if isscalar(p.radialrange), p.radialrange = [0; p.radialrange]; end
if length(p.radialrange) == 2
p.radialrange = linspace(p.radialrange(1),p.radialrange(2),Zrows).';
end
if ~ismono(p.radialrange)
error('''RadialRange'' vector must be monotonic');
end
if length(p.radialrange) ~= Zrows
error('''RadialRange'' size must match number of rows in input matrix');
end
% Angular and radial range vectors define data order
[Tmin,Tmax] = deal(min(p.angularrange),max(p.angularrange)); % Tmin < Tmax
[Rmin,Rmax] = deal(min(p.radialrange ),max(p.radialrange )); % Rmin < Rmax
% Reflect Zp left-right and/or up-down depending on angular and radial ranges
if p.angularrange(1) > p.angularrange(end), Zp = fliplr(Zp); end
if p.radialrange (1) > p.radialrange (end), Zp = flipud(Zp); end
% Angular range cannot be more than one full circumference
if abs(Tmax - Tmin) > 2*pi
error('Angular range cannot be greater than 2*pi');
end
% Check radial and azimuthal polar grid density
p.polargrid = p.polargrid(:);
if length(p.polargrid) ~= 2 || ~iscell(p.polargrid)
error('''PolarGrid'' property value must be a two element numeric cell array');
end
[radgrid,azmgrid] = deal(p.polargrid{1},p.polargrid{2});
% Check azimuthal grid density
if ~isnumeric(azmgrid)
error('Non-numeric azimuthal grid parameter');
end
if isempty(azmgrid) || (length(azmgrid)==1 && azmgrid == 0)
azmgrid = 1;
end
if length(azmgrid) == 1
azmgrid = linspace(Tmin,Tmax,azmgrid+1).';
end
% Check radial grid density
if ~isnumeric(radgrid)
error('Non-numeric radial grid parameter');
end
if isempty(radgrid) || (length(radgrid)==1 && radgrid==0)
radgrid = 1;
end
if length(radgrid) == 1
radgrid = linspace(Rmin,Rmax,radgrid+1).';
end
% Check number of radial grid labels
if ~isnumeric(p.radlabels)
error('''RadialLablels'' property value must be numeric');
end
% Check radial label location, {azimuthal, sagittal}
if isempty(p.radlabellocation)
p.radlabellocation = {0 'surf'};
end
if ~iscell(p.radlabellocation) || numel(p.radlabellocation) ~= 2
error('''RadLabelLocation'' property must be a 2 element cell array');
end
azimuth_radax = p.radlabellocation{1};
if ~isnumeric(azimuth_radax) || azimuth_radax < 0 || azimuth_radax > 360
error('azimuthal radial location must be between 0 and 360 degrees');
end
p.radlabellocation = p.radlabellocation{2};
if isnumeric(p.radlabellocation)
sagittal_radax = p.radlabellocation;
p.radlabellocation = 'const';
end
if ~ischar(p.radlabellocation) || isempty(matchstr2lst(lower(p.radlabellocation),rlst))
error('Invalid ''RadLabelLocation'' property value');
end
p.radlabellocation = lower(p.radlabellocation);
% Check mesh scale factor property value
if isscalar(p.meshscale), p.meshscale = [p.meshscale p.meshscale]; end
if ~isnumeric(p.meshscale) || any(p.meshscale <= 0)
error('''MeshScale'' property values must be positive and real');
end
% Check tick spacing property value
if ~isempty(p.tickspacing) && (~isscalar(p.tickspacing) || ~isnumeric(p.tickspacing))
error('''TickSpacing'' property value must be a scalar numeric value or empty');
end
% Check contour lines property value
if ~isnumeric(p.contourlines) && ~isequal(p.contourlines,'')
error('''ContourLines'' property value must be numeric');
end
p.contourlines = p.contourlines(:);
% Check grid line style property value, default depends on plottype
if isempty(p.gridstyle)
if isequal(p.plottype,'contour'), p.gridstyle = ':'; % dotted line
else p.gridstyle = '-'; % solid line
end
end
if ~ischar(p.gridstyle) || isempty(matchstr2lst(lower(p.gridstyle),glst))
error('Invalid ''GridStyle'' property value');
end
% Check interpolation method
p.interpmethod = lower(p.interpmethod);
if ~ischar(p.interpmethod) || isempty(matchstr2lst(p.interpmethod,mlst))
error('Invalid ''InterpMethod'' property value');
end
% Check if mesh scale factor is compatible with input data dimension
if round(min([Zrows Zcols]./p.meshscale)) < 4
error('Mesh scale factor is too large, not enough data remaining to plot');
end
% Choose default color matrix
if isempty(p.colordata), p.colordata = Zp; end % surface height coloring
% Check color matrix
if ~isnumeric(p.colordata) || ~isequal(size(p.colordata),[Zrows,Zcols])
error('Color matrix must be numeric and the same size as Zp');
end
% Polar axis z location is a constant for contour plots
if isequal(p.plottype,'contour') && ~isequal(p.axislocation,'off')
polax = 0;
p.axislocation = 'const';
end
% Check Cartesian origin
if isempty(p.cartorigin) || ~isnumeric(p.cartorigin)
error('Cartesian origin must be numeric');
end
if length(p.cartorigin)<3, p.cartorigin(3) = 0; end
%-- Create polar grid and interpolate data
% Create radius and angle vectors and polar grid for input data matrix
rho = p.radialrange; % radius vector
angl = p.angularrange; % angle vector
[xx,yy] = meshgrid(angl,rho); % mesh matrices
Zi = Zp; % z's == input data
Ci = p.colordata; % colormap
% No interpolation for uniform scaling
if isequal(p.meshscale,[1 1]) % uniform scaling
Xi = rho * cos(angl.'); % matrix of x's
Yi = rho * sin(angl.'); % matrix of y's
% Create a new grid and interpolation data
else
q = fix([Zrows Zcols]./p.meshscale); % new mesh size
rho = linspace(Rmin,Rmax,q(1)); % radius vector
angl = linspace(Tmin,Tmax,q(2)); % angle vector
[theta,rad] = meshgrid(angl,rho); % create polar grid
T = interp2(xx,yy,Zp,theta,rad,p.interpmethod); % interpolate Zp to grid
Ci = interp2(xx,yy,Ci,theta,rad,p.interpmethod); % interpolate color
[Xi,Yi,Zi] = pol2cart(theta,rad,T); % convert to Cartesian
end
% Swap x,y for clockwise polar plot
if isequal(p.polardirection,'cw')
[Xi,Yi] = deal(Yi,Xi);
end
% Offset grid data to Cartesian origin
Xi = Xi + p.cartorigin(1);
Yi = Yi + p.cartorigin(2);
Zi = Zi + p.cartorigin(3);
%-- Plot the surface
switch p.plottype
case 'wire', grid on;
case 'meshc', mesh(Xi,Yi,Zi,Ci);
addcontours(Xi,Yi,Zi,p.contourlines);
case 'mesh', mesh (Xi,Yi,Zi,Ci);
case 'surf', surf (Xi,Yi,Zi,Ci);
case 'surfc', surf (Xi,Yi,Zi,Ci);
addcontours(Xi,Yi,Zi,p.contourlines);
case 'surfn', surf (Xi,Yi,Zi,Ci,'LineStyle','none');
case 'surfcn', surf (Xi,Yi,Zi,Ci,'LineStyle','none');
addcontours(Xi,Yi,Zi,p.contourlines);
case 'contour', contour(Xi,Yi,Zi,p.contourlines);
axis equal; xlim(xlim*1.1); ylim(ylim*1.1);
set(gca,'visible','off');
set(get(gca,'xlabel'),'visible','on');
set(get(gca,'ylabel'),'visible','on');
set(get(gca,'title'), 'visible','on');
end
%-- Plot the polar axis
% Axis and tick label attributes
fontargs = {'FontName','Arial','FontSize',10,'FontWeight','bold'};
if ~isequal(p.axislocation,'off')
% Create polar axis data just outside the largest radius
xa = Rmax * 1.005 .* cos(angl) + p.cartorigin(1);
ya = Rmax * 1.005 .* sin(angl) + p.cartorigin(2);
% Set polax to z location of polar axis
switch p.axislocation
case 'min', polax = min (Zi(end,:));
case 'max', polax = max (Zi(end,:));
case 'mean', polax = mean(Zi(end,:));
case 'top', zlim = get(gca,'zlim'); polax = zlim(2);
case 'bottom', zlim = get(gca,'zlim'); polax = zlim(1);
end
% Z values for polar axis
if isequal(p.axislocation,'surf')
za = Zi(end,:); % vary along edge of surface
else za = zeros(size(xa)) + polax; % constant location
end
% Swap x,y for clockwise polar plot
if isequal(p.polardirection,'cw'), [xa,ya] = deal(ya,xa); end
% Plot the polar axis
hold on;
line(xa,ya,za,'Color',p.polaraxiscolor,'LineWidth',1);
% Add tick marks and labels
if ~isempty(p.tickspacing) && p.tickspacing > 0 && p.tickspacing <= 180
% Create polar axis tic marks at p.tickspacing intervals
ts = 180/p.tickspacing;
ta = pi/ts * (round(Tmin*ts/pi):1:round(Tmax*ts/pi));
tr = Rmax * [1.005; 1.03; 1.1];
xt = tr * cos(ta) + p.cartorigin(1);
yt = tr * sin(ta) + p.cartorigin(2);
% Z values for polar tick marks
if isequal(p.axislocation,'surf')
zt = interp1(angl,za,ta,'linear'); % vary along edge of surface
else zt = zeros(1,length(ta)) + polax; % constant location
end
% Label every other tick mark
nl = round(length(ta)/2);
% Beginning and end of a full polar axis are identical, label once
if abs(Tmin-Tmax) == 2*pi, nl = nl-1; end
% Swap x,y for clockwise polar plot
if isequal(p.polardirection,'cw'), [xt,yt] = deal(yt,xt); end
% Draw the tick marks
line(xt(1:2,:),yt(1:2,:),[zt; zt],'Color',p.tickcolor);
% Add tick labels
for k = 2 * (1:nl) - 1
text(xt(3,k),yt(3,k),zt(k),num2str(ta(k)*180/pi),...
'HorizontalAlignment','Center',fontargs{:},'Color',p.ticklabelcolor);
end
end
hold off;
end
% -- Add radial labels
if p.radlabels > 0 && ~isequal(p.radlabellocation,'off')
% Find x,y locations of radial labels
ta = azimuth_radax*pi/180;
tr = linspace(Rmin,Rmax,p.radlabels+2);
tr(1) = []; tr(end) = []; % delete inner,outer labels
xt = tr * cos(ta) + p.cartorigin(1);
yt = tr * sin(ta) + p.cartorigin(2);
% Swap x,y for clockwise polar plot
if isequal(p.polardirection,'cw'), [xt,yt] = deal(yt,xt); end
% Set sagittal_radax to z location of radial labels
switch p.radlabellocation
case 'min', sagittal_radax = min (Zi(:));
case 'max', sagittal_radax = max (Zi(:));
case 'mean', sagittal_radax = mean(Zi(:));
case 'top', zlim = get(gca,'zlim'); sagittal_radax = zlim(2);
case 'bottom', zlim = get(gca,'zlim'); sagittal_radax = zlim(1);
end
% Z values for radial labels
if isequal(p.radlabellocation,'surf')
zt = interp2(xx,yy,Zp,ta,tr,p.interpmethod); % vary with surface
else
sagittal_radax(length(tr)+1:end) = [];
sagittal_radax(end+1:length(tr)) = sagittal_radax(end);
zt = zeros(1,length(tr)) + sagittal_radax; % constant location
end
% Add radial labels to the plot
hold on;
for k = 1:length(tr)
text(xt(k),yt(k),zt(k),num2str(tr(k)),...
'HorizontalAlignment','Center',fontargs{:},'Color',p.radlabelcolor);
end
hold off;
end
%-- Draw polar grid lines
r = length(radgrid);
m = length(azmgrid);
% Is there more than one radial or azimuthal section?
if r > 1 || m > 1
% Allocate space for grid lines
u = p.gridscale(1);
v = p.gridscale(2);
rho = zeros(1,(r-1)*u+1);
angl = zeros(1,(m-1)*v+1);
% Fill in grid vectors between points so the interpolated grid follows
% surface contours smoothly and the azimuthal grid lines are circular.
for k = 1:r-1
rho((k-1)*u+1:(k*u)+1) = linspace(radgrid(k),radgrid(k+1),u+1);
end
for k = 1:m-1
angl((k-1)*v+1:(k*v)+1) = linspace(azmgrid(k),azmgrid(k+1),v+1);
end
% Interpolate data to a fine mesh to produce smooth grid lines
[theta,rad] = meshgrid(angl,rho); % create polar grid
T = interp2(xx,yy,Zp,theta,rad,p.interpmethod); % interpolate to grid
[xi,yi,zi] = pol2cart(theta,rad,T); % convert to Cartesian
xi = xi + p.cartorigin(1); % translate center, x
yi = yi + p.cartorigin(2); % y
zi = zi + p.cartorigin(3); % z
% Swap x,y for clockwise polar plot
if isequal(p.polardirection,'cw')
[xi,yi] = deal(yi,xi);
end
% In-plane grid lines for contour plots
if isequal(p.plottype,'contour'), zi = zeros(size(zi)); end
% draw radial grid lines in azimuthal direction (meridians)
hold on;
if length(azmgrid) > 2
xr = xi(:,1:u:end);
yr = yi(:,1:u:end);
zr = zi(:,1:u:end);
plot3(xr,yr,zr,p.gridstyle,'Color',p.gridcolor,'LineWidth',1);
end
% Draw azimuthal grid lines in radial direction (concentric arcs)
if length(radgrid) > 2
xm = xi(1:v:end,:);
ym = yi(1:v:end,:);
zm = zi(1:v:end,:);
plot3(xm',ym',zm',p.gridstyle,'Color',p.gridcolor,'LineWidth',1);
end
hold off;
end
%-- Set axis font
if ~isequal(p.plottype,'off'), set(gca,fontargs{:}); end
%-- Apply additional properties to the plot
if ~isempty(p.plotprops), set(gca,p.plotprops{:}); end
end
%-- Local functions
% Add a contour plot to the current surface or mesh plot
function addcontours(x,y,z,levels)
if isempty(levels), levels = 16; end
hold on;
a = get(gca,'zlim');
zpos = a(1); % find smallest z value in 3d plot
% Add contours
[~,hh] = contour3(x,y,z,levels);
% Change all contour group positions to bottom of plot
for j = 1:length(hh)
zz = get(hh(j),'Zdata');
set(hh(j),'Zdata',zpos*ones(size(zz)));
end
end
% Structure reconciliation with a template
function [T,S] = 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
S = rmfield(S,fname{k});
end
end
end
% Convert an argument pairs cell array 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
% Convert a structure to an argument pairs cell array
function P = struct2pv(S)
% Check input argument
if ~isstruct(S), P = {}; return; end
% Get field names
n = fieldnames(S);
% Convert structure values to cell array
v = struct2cell(S);
% Combine names and values, return a 1xN cell array
P = {n{:}; v{:}};
P = P(:).';
end
% Match a string with a list of strings
function idx = matchstr2lst(str,strarray,opt)
if nargin < 2, return; end
idx = find(strncmpi(str,strarray,length(str))==1);
idx = idx(:);
if nargin > 2
if strcmpi(opt,'first'), idx = idx(1);
elseif strcmpi(opt,'last'), idx = idx(end);
end
end
end
% Test monotonicity of a vector
function m = ismono(v)
[r,c] = size(v); % size of input
if r == 1, % row vector
v = v'; % transpose
r = c; c = 1; % size
end
sgn = sum(sign(diff(v,1,1)),1); % sgn is r+1 or -(r+1) for monotonic columns
up = sgn - repmat(r,1,c); % subtract r to detect increasing (-1)
down = sgn + repmat(r,1,c); % add r to detect decreasing (+1)
up (up < -1) = 0; % force non-monotonic entries to zero
down(down > 1) = 0;
m = -(up + down); % flip sense of output so +1 is increasing
end