Moving the "anchor point" of patches in Matlab surf-plots - matlab

MWE below
I'm plotting an energy surface using Matlabs surf function. I first create my data points at various azimuthal and elevation angles, and next I convert these to an (x,y,z)-coordinate using the usual spherical-to-cartesian equations, and reshape the coordinates into a matrix.
However, when I plot this, I find that all data points are represented as the lower left corner of a "patch" of color, instead of centrally in the patch of color.
However, when I am scaling the radius of the surface to reflect the energy at that point, any dimples in the surface has all messed up colors due to this effect, i.e. any symmetry of the data is lost in the visualization. I would like this dimple to have roughly symmetrical color around the deepest point here.
The example shown is with the conversion from spherical to cartesian coordinates shifted by half the angular difference between two data points - this means that patches are centered around the right spot, but the coloring is still off.
Is there a way to "center" the colored patches on the exact direction that the data was taken?
scaling = 100;
min_val = 0.5
count = 1;
azstep = 10;
elstep = 10;
az = 0:azstep:360;
el = -90:elstep:90;
for ii = 1:length(az)
for jj = 1:length(el)
if any(az(ii) == [260 270 280]) && any(el(jj) == [-10 0 10])
r_output(count) = 0.8;
else
r_output(count) = 1;
end
c(count) = r_output(count);
r(count) = 1 + scaling.*(r_output(count)/min_val) - scaling;
x(count) = (r(count)) .* cosd(el(jj)) .* cosd(az(ii));
y(count) = (r(count)) .* cosd(el(jj)) .* sind(az(ii));
z(count) = (r(count)) .* sind(el(jj));
count = count + 1;
end
end
X = reshape(x,length(el),length(az));
Y = reshape(y,length(el),length(az));
Z = reshape(z,length(el),length(az));
C = reshape(c,length(el),length(az));
figure
hold on
surf(X,Y,Z,C)
colorbar
axis equal
xlabel('X axis'); ylabel('Y axis'); zlabel('Z axis');
Result from code above

Change the shading!
Adding shading interp in the end of your code produces the following image:
if you want the lines, then change the surf call to:
h=surf(X,Y,Z,C)
colorbar
shading interp
set(h','edgecolor','k')
as shading will remove them.
Note that you still get some artefacts in some corners. They seem to be generated by floating point errors when interpolating, so the only way of improving them seems to be just making a more densely populated sphere.

Related

How can I reduce the number of mesh lines shown in a surface plot?

I've found this answer, but I can't complete my work. I wanted to plot more precisely the functions I am studying, without overcoloring my function with black ink... meaning reducing the number of mesh lines. I precise that the functions are complex.
I tried to add to my already existing code the work written at the link above.
This is what I've done:
r = (0:0.35:15)'; % create a matrix of complex inputs
theta = pi*(-2:0.04:2);
z = r*exp(1i*theta);
w = z.^2;
figure('Name','Graphique complexe','units','normalized','outerposition',[0.08 0.1 0.8 0.55]);
s = surf(real(z),imag(z),imag(w),real(w)); % visualize the complex function using surf
s.EdgeColor = 'none';
x=s.XData;
y=s.YData;
z=s.ZData;
x=x(1,:);
y=y(:,1);
% Divide the lengths by the number of lines needed
xnumlines = 10; % 10 lines
ynumlines = 10; % 10 partitions
xspacing = round(length(x)/xnumlines);
yspacing = round(length(y)/ynumlines);
hold on
for i = 1:yspacing:length(y)
Y1 = y(i)*ones(size(x)); % a constant vector
Z1 = z(i,:);
plot3(x,Y1,Z1,'-k');
end
% Plotting lines in the Y-Z plane
for i = 1:xspacing:length(x)
X2 = x(i)*ones(size(y)); % a constant vector
Z2 = z(:,i);
plot3(X2,y,Z2,'-k');
end
hold off
But the problem is that the mesh is still invisible. How to fix this? Where is the problem?
And maybe, instead of drawing a grid, perhaps it is possible to draw circles and radiuses like originally on the graph?
I found an old script of mine where I did more or less what you're looking for. I adapted it to the radial plot you have here.
There are two tricks in this script:
The surface plot contains all the data, but because there is no mesh drawn, it is hard to see the details in this surface (your data is quite smooth, this is particularly true for a more bumpy surface, so I added some noise to the data to show this off). To improve the visibility, we use interpolation for the color, and add a light source.
The mesh drawn is a subsampled version of the original data. Because the original data is radial, the XData and YData properties are not a rectangular grid, and therefore one cannot just take the first row and column of these arrays. Instead, we use the full matrices, but subsample rows for drawing the circles and subsample columns for drawing the radii.
% create a matrix of complex inputs
% (similar to OP, but with more data points)
r = linspace(0,15,101).';
theta = linspace(-pi,pi,101);
z = r * exp(1i*theta);
w = z.^2;
figure, hold on
% visualize the complex function using surf
% (similar to OP, but with a little bit of noise added to Z)
s = surf(real(z),imag(z),imag(w)+5*rand(size(w)),real(w));
s.EdgeColor = 'none';
s.FaceColor = 'interp';
% get data back from figure
x = s.XData;
y = s.YData;
z = s.ZData;
% draw circles -- loop written to make sure the outer circle is drawn
for ii=size(x,1):-10:1
plot3(x(ii,:),y(ii,:),z(ii,:),'k-');
end
% draw radii
for ii=1:5:size(x,2)
plot3(x(:,ii),y(:,ii),z(:,ii),'k-');
end
% set axis properties for better 3D viewing of data
set(gca,'box','on','projection','perspective')
set(gca,'DataAspectRatio',[1,1,40])
view(-10,26)
% add lighting
h = camlight('left');
lighting gouraud
material dull
How about this approach?
[X,Y,Z] = peaks(500) ;
surf(X,Y,Z) ;
shading interp ;
colorbar
hold on
miss = 10 ; % enter the number of lines you want to miss
plot3(X(1:miss:end,1:miss:end),Y(1:miss:end,1:miss:end),Z(1:miss:end,1:miss:end),'k') ;
plot3(X(1:miss:end,1:miss:end)',Y(1:miss:end,1:miss:end)',Z(1:miss:end,1:miss:end)','k') ;

Specifying different colour schemes for different orientations of Polarhistogram in MATLAB

Question
When using polarhistogram(theta) to plot a dataset containing azimuths from 0-360 degrees. Is it possible to specify colours for given segments?
Example
In the plot bellow for example would it be possible to specify that all bars between 0 and 90 degrees (and thus 180-270 degrees also) are red? whilst the rest remains blue?
Reference material
I think if it exists it will be within here somewhere but I am unable to figure out which part exactly:
https://www.mathworks.com/help/matlab/ref/polaraxes-properties.html
If you use rose, you can extract the edges of the histogram and plot each bar one by one. It's a bit of a hack but it works, looks pretty and does not require Matlab 2016b.
theta = atan2(rand(1e3,1)-0.5,2*(rand(1e3,1)-0.5));
n = 25;
colours = hsv(n);
figure;
rose(theta,n); cla; % Use this to initialise polar axes
[theta,rho] = rose(theta,n); % Get the histogram edges
theta(end+1) = theta(1); % Wrap around for easy interation
rho(end+1) = rho(1);
hold on;
for j = 1:floor(length(theta)/4)
k = #(j) 4*(j-1)+1; % Change of iterator
h = polar(theta(k(j):k(j)+3),rho(k(j):k(j)+3));
set(h,'color',colours(j,:)); % Set the color
[x,y] = pol2cart(theta(k(j):k(j)+3),rho(k(j):k(j)+3));
h = patch(x,y,'');
set(h,'FaceColor',colours(j,:),'FaceAlpha',0.2);
uistack(h,'down');
end
grid on; axis equal;
title('Coloured polar histogram')
Result

Generating a curve around the point on a scatter plot

I am generating a scatter plot containing data from multiple sources, as displayed below.
I would like to be able to generate a curve surrounding an arbitrary query point and passing through points on scatter plot. Final goal is to calculate the area between the lines on the plot.
I have implemented solution using finding points with knnsearch in a circular fashion and then applying hampel filter to eliminate noise. In the example below, I have selected a point right about in the middle of the blue-shaded area. As you can see, the result is far from perfect, and I need more precision.
I am looking for something similar to boundary function, but to work from the inside of the point cloud, not from the outside.
Final goal is to calculate the area between the lines on the plot.
I would do it differently. Just take any two lines of the plot, calculate the area under the curves with some kind of numerical approximation (for example trapezoidal numerical integration), then subtract the areas and obtain the area between the lines.
Thank to idea in Trilarion's answer, I was able to come up with the better solution.
Note that I use notation for YZ plane instead of XY (to keep consistent with robot coordinate system).
Solution
Generate curves for each set of scatter data
% Scatter data is in iy and iz vectors.
curve = fit(iy, iz, 'smoothingspline', 'SmoothingParam', 0.5);
% Remove outliers.
fdata = feval(curve, iy);
I = abs(fdata - iz) > 0.5 * std(iz);
outliers = excludedata(iy, iz, 'indices', I);
% Final curve without outliers.
curve = fit(iy, iz, 'smoothingspline', 'Exclude', outliers, 'SmoothingParam', 0.5);
Plot curves and scatter data
% Color maps generated by MATLAB's colormap function.
h_curve = plot(curve);
set(h_curve, 'Color', color_map_light(i,:));
scatter(iy, iz, '.', 'MarkerFaceColor', color_map(i,:))
Let user provide an input by selecting points
User selects one point as a query point and two points for limits along Y axis. This is because some curves come close, but never intersect.
[cs_position.y, cs_position.z] = ginput(1);
[cs_area_limits, ~] = ginput(2);
if cs_area_limits(1) > cs_area_limits(2)
cs_area_limits = flipud(cs_area_limits);
end
plot_cross_section(cs_position);
Finally calculate and plot surface area
This section uses fantastic answer by Doresoom.
function [ ] = plot_cross_section(query_point)
%PLOT_CROSS_SECTION Calculates and plots cross-section area.
% query_point Query point.
% Find values on query point's Y on each of the curves.
z_values = cellfun(#(x, y) feval(x, y),...
curves, num2cell(ones(size(curves)) * query_point.y))
% Find which curves are right above and below the query point.
id_top = find(z_values >= query_point.z, 1, 'first')
id_bottom = find(z_values < query_point.z, 1, 'last')
if isempty(id_top) || isempty(id_bottom)
return
end
% Generate points along curves on the range over Y.
y_range = cs_area_limits(1):0.1:cs_area_limits(2);
z_top = feval(curves{id_top}, y_range).';
z_bottom = feval(curves{id_bottom}, y_range).';
% Plot area.
Y = [ y_range, fliplr(y_range) ];
Z = [ z_top, fliplr(z_bottom) ];
fill(Y, Z, 'b', 'LineStyle', 'none')
alpha 0.5
hold on
% Calculate area and show to user.
cs_area = polyarea(Y, Z);
area_string = sprintf('%.2f mm^2', cs_area);
text(0, -3, area_string, 'HorizontalAlignment', 'center')
end
Result

Rotating a Plot About the Y Axis

I have a vector of values that I want to plot as brightness on a circle through the radius of it (I.e. If it was 0 3 1 5 I'd want a circle that was dark at the centre, then a bright ring around it, then a slightly darker ring, then a brighter ring).
To do this I've attempted to rotate my radial vector (E) around the y axis, as such
[X,Y,Z] = cylinder(E);
h = surf(X,Y,Z),
However I'm clearly not doing it right, as this appears to be rotating my curve around the x axis. I've tried just swapping X and Y, but it still rotates it around the x axis. Any help would be greatly appreciated.
One way would be to rotate your vector and create a surface. The Z data of the surface (your rotated vector) will be color coded according to the colormap you choose, if you display the surface from the top you get your circles at the different brightness.
If you are really only interested from the "top view" of this surface, then no need to create a full surface, a simple pcolor will do the job.
example:
%% // input data (and assumptions)
E=[0 3 1 5 2 7];
nBrightness = 10 ; %// number of brightness levels
r = (0:numel(E)) ; %// radius step=1 by default for consecutive circles
%// otherwise define different thickness for each circle
So if I use stairs([E 0]) you get your different brightness levels:
I had to add a last 0 to the vector to "close" the last level, we'll have to do that again in the solution below.
Now to rotate/replicate that around Y, color code the height, and look at it from the top:
%% // replicate profile around axis
ntt = 50 ; %// define how many angular division for the plot
theta = linspace(0,2*pi,ntt) ; %// create all the angular divisions
[rr,tt]=meshgrid(r,theta) ; %// generate a grid
z = repmat( [E 0] , ntt , 1 ) ; %// replicate our "E" vector to match the grid
[xx,yy,zz] = pol2cart(tt,rr,z) ; %// convert everything to cartesian coordinates
pcolor(xx,yy,zz) %// plot everything
colormap(gray(nBrightness)) %// make sure we use only "nBrightness" colors (Shades of gray)
caxis([0 nBrightness])
shading flat ; axis equal %// refine the view (axis ratio and "spokes" not visible) etc...
colorbar
axis off
will yield the following :
Note that your problem was not fully defined, I had to take assumptions on:
What radius each brightness circle should have ? (I made them all the same but you can modify that)
How many brightness levels you want ? (You can also modify that easily though).
Have you tried the rotate function?
direction = [0 1 0];
rotate(h,direction,90);
In this example a 90 degree rotation is performed around the y axis.
Using this library http://www.mathworks.com/matlabcentral/fileexchange/45952-circle-plotter
%http://www.mathworks.com/matlabcentral/fileexchange/45952-circle-plotter
x0 = 0;
y0 = 0;
colors = [0 3 1 5];
maxC = max(colors);
sz = numel(colors);
for i=fliplr(1:sz)
c = colors(i);
circles(x0,y0,i,'facecolor',[c/maxC c/maxC 0]) % http://au.mathworks.com/help/matlab/ref/colorspec.html
end

Programmatically producing polar or quasi-polar plots with a variable for color in matlab

I would like to create plots using matlab that represent a numerical assessment of quality in a radial fashion.
The best method I've found seems to not work properly. One runs the following code:
theta = (0 : (360/11) : 360)*pi/180;
r = 0 : 2 : 20 ;
[TH,R] = meshgrid(theta,r);
[X,Y] = pol2cart(TH,R);
Z = meshgrid(Data);
surf(X,Y,Z);
Data is a vector of data containing 11 numbers, an example dataset being the following:
Data = 0.884, 0.882, 0.879, 0.880, 0.8776, 0.871, 0.8587, 0.829, 0.811, 0.803, 0.780
the output of surf here is this:
I would like to produce a more refined version of this type of image:
which I have generated with the following code:
for theta = 0 : pi/100 : pi;
v = [InterpolatedImageHeight;LengthVector];
x_center = InterpolatedImageHeight((HorizontalRes+1)/2);
y_center = 0; %InterpolatedImageHeight((HorizontalRes+1)/2);
center = repmat([x_center; y_center], 1, length(InterpolatedImageHeight));
R = [cos(theta) -sin(theta); sin(theta) cos(theta)];
vo = R*(v - center) + center;
x_rotated = vo(1,:);
y_rotated = vo(2,:);
scatter(x_rotated,y_rotated,DotSize,InterpolatedData,'filled'); %x,y,area,color,properties
end
The issue with this is that it is a scatter plot where I am essentially using plot(r,Data), plotting many many copies, and increasing the dot size. The graphic itself has many seams, this takes an enormous amount of memory, and is time intensive where surf or mesh will run extremely fast and take minimal memory.
How does one produce concentric rings with a variable input for color?
There are two completely different plots in your question. The first one represents the data as rays from the origin towards the outside of the circle. The data-points are placed anti-clockwise. A refined version of this can be achieved like this:
Data = [0.884, 0.882, 0.879, 0.880, 0.8776, 0.871,...
0.8587, 0.829, 0.811, 0.803, 0.780];
theta = linspace(0,2*pi,length(Data));
r = linspace(0,20,length(Data));
[TH,R] = meshgrid(theta,r);
Z = meshgrid(Data);
[X,Y,Z] = pol2cart(TH,R,Z);
surf(X,Y,Z);
view(2);
shading interp
Note that I used linspace to generate theta and r to always match the length of Data. Z is also passed trough pol2cart. Then you can use shading interp to remove the lines between the patches and interpolate the color. With view(2) you can set the perspective as you would have a 2d-plot.
This is the result:
It's relatively easy to get a result like in your second example. There the data-points represent concentric circles around the origin and are placed from the origin towards the outside. Therefore, just transpose the meshgrid of Z by using the following line:
Z = meshgrid(Data)';
This is the result then:
based on the code by Darren Rowland in this thread I have come up with the following solution:
x = interp1(1:length(data),datax,(datax(1):datax(end)/f:datax(end)),'linear');
y = interp1(1:length(datay),datay,datay(1):datay(end)/f:datay(end),'spline');
theta = linspace(0,2*pi,n);
xr = x.'*cos(theta);
zr = x.'*sin(theta);
yr = repmat(y.',1,n);
figure;
surf(xy,yr,zr,zr*numcolors);
which is elegant, runs quickly, and produces beautiful figures. This is a sample of the output with some extra chart elements: