How to plot 3D path data as a ribbon in Matlab - matlab

I have scatter data X1,Y1,Z1 in 3D, which I can plot as
a=1; c=1; t=0:100;
X1 = (a*t/2*pi*c).*sin(t);
Y1 = (a*t/2*pi*c).*cos(t);
Z1 = t/(2*pi*c);
scatter3(X1,Y1,Z1);
% or plot3(X1,Y1,Z1);
The points define a 3D path. How do I make this into a ribbon plot, similar to the one below?
With delaunay triangulation I can plot it as a surface:
tri = delaunay(X1,Y1);
h = trisurf(tri, X1, Y1, Z1);
But ribbon does not give the desired result:
ribbon(Y1)
The figure below shows what I am after.

The ribbon function can only accept 2D inputs because it uses the 3rd dimension to 'build' the ribbon.
One way to achieve a 3D ribbon is to build series of patch or surface between each point and orient them properly so they look continuous.
The following code will build a ribbon around any arbitrary 3D path defined by an (x,y,z) vector. I will not explain each line of the code but there are plenty of comments and I stopped for intermediate visualisations so you can understand how it is constructed.
%% Input data
a=1; c=1; t=0:.1:100;
x = (a*t/2*pi*c).*sin(t);
y = (a*t/2*pi*c).*cos(t);
z = t/(2*pi*c);
nPts = numel(x) ;
%% display 3D path only
figure;
h.line = plot3(x,y,z,'k','linewidth',2,'Marker','none');
hold on
xlabel('X')
ylabel('Y')
zlabel('Z')
%% Define options
width = ones(size(x)) * .4 ;
% define surface and patch display options (FaceAlpha etc ...), for later
surfoptions = {'FaceAlpha',0.8 , 'EdgeColor','k' , 'EdgeAlpha',0.8 , 'DiffuseStrength',1 , 'AmbientStrength',1 } ;
%% get the gradient at each point of the curve
Gx = diff([x,x(1)]).' ;
Gy = diff([y,y(1)]).' ;
Gz = diff([z,z(1)]).' ;
% get the middle gradient between 2 segments (optional, just for better rendering if low number of points)
G = [ (Gx+circshift(Gx,1))./2 (Gy+circshift(Gy,1))./2 (Gz+circshift(Gz,1))./2] ;
%% get the angles (azimuth, elevation) of each plane normal to the curve
ux = [1 0 0] ;
uy = [0 1 0] ;
uz = [0 0 1] ;
for k = nPts:-1:1 % running the loop in reverse does automatic preallocation
a = G(k,:) ./ norm(G(k,:)) ;
angx(k) = atan2( norm(cross(a,ux)) , dot(a,ux)) ;
angy(k) = atan2( norm(cross(a,uy)) , dot(a,uy)) ;
angz(k) = atan2( norm(cross(a,uz)) , dot(a,uz)) ;
[az(k),el(k)] = cart2sph( a(1) , a(2) , a(3) ) ;
end
% compensate for poor choice of initial cross section plane
az = az + pi/2 ;
el = pi/2 - el ;
%% define basic ribbon element
npRib = 2 ;
xd = [ 0 0] ;
yd = [-1 1] ;
zd = [ 0 0] ;
%% Generate coordinates for each cross section
cRibX = zeros( nPts , npRib ) ;
cRibY = zeros( nPts , npRib ) ;
cRibZ = zeros( nPts , npRib ) ;
cRibC = zeros( nPts , npRib ) ;
for ip = 1:nPts
% cross section coordinates.
csTemp = [ ( width(ip) .* xd ) ; ... %// X coordinates
( width(ip) .* yd ) ; ... %// Y coordinates
zd ] ; %// Z coordinates
%// rotate the cross section (around X axis, around origin)
elev = el(ip) ;
Rmat = [ 1 0 0 ; ...
0 cos(elev) -sin(elev) ; ...
0 sin(elev) cos(elev) ] ;
csTemp = Rmat * csTemp ;
%// do the same again to orient the azimuth (around Z axis)
azi = az(ip) ;
Rmat = [ cos(azi) -sin(azi) 0 ; ...
sin(azi) cos(azi) 0 ; ...
0 0 1 ] ;
csTemp = Rmat * csTemp ;
%// translate each cross section where it should be and store in global coordinate vector
cRibX(ip,:) = csTemp(1,:) + x(ip) ;
cRibY(ip,:) = csTemp(2,:) + y(ip) ;
cRibZ(ip,:) = csTemp(3,:) + z(ip) ;
end
%% Display the full ribbon
hd.cyl = surf( cRibX , cRibY , cRibZ , cRibC ) ;
set( hd.cyl , surfoptions{:} )
Now you have your graphic object contained in one surface object, you can set the options for the final rendering. For example (only an example, explore the surface object properties to find all te possibilities).
%% Final render
h.line.Visible = 'off' ;
surfoptionsfinal = {'FaceAlpha',0.8 , 'EdgeColor','none' , 'DiffuseStrength',1 , 'AmbientStrength',1 } ;
set( hd.cyl , surfoptionsfinal{:} )
axis off
Note that this code is an adaptation (simplification) of the code provided in this answer (to that question: Matlab: “X-Ray” plot line through patch).
This method allows to draw an arbitrary cross section (a disc in the answer) and build a surface which will follow a path. For your question I replaced the disc cross section by a simple line. You could also replace it with any arbitrary cross section (a disc, a square, a potatoid ... the sky is the limit).
Edit
Alternative Method:
Well it turns out there is a Matlab function which can do that. I first discarded it because it is meant for 3D volume visualisations, and most ways to call it require gridded input (meshgrid style). Luckily for us, there is also a calling syntax which can work with your data.
% Same input data
a=1; c=1; t=0:.1:100;
x = (a*t/2*pi*c).*sin(t);
y = (a*t/2*pi*c).*cos(t);
z = t/(2*pi*c);
% Define vertices (and place in cell array)
verts = {[x.',y.',z.']};
% Define "twistangle". We do not need to twist it in that direction but the
% function needs this input so filling it with '0'
twistangle = {zeros(size(x.'))} ;
% call 'streamribbon', the 3rd argument is the width of the ribbon.
hs = streamribbon(verts,tw,0.4) ;
% improve rendering
view(25,9)
axis off
shading interp;
camlight
lighting gouraud
Will render the following figure:
For additional graphic control (over the edges of the ribbon), you can refer to this question and my answer: MATLAB streamribbon edge color

Related

How to prevent an object from being illuminated by camlight in Matlab?

[Hereafter are 4 snippets, one should only be interested in reading the two first ones. However by copy-pasting all of these, one should be able to launch what I see, although screenshots are provided at the end.]
Hi, by launching this main.m :
%To see if plotting a tick after setting camlight headlight will leads to
%its background becoming gray or not
%clear all
figure
arrow = arrow3D([0 0 0], [1 1 1], 'r', 0.8, 0.2, 1.5);
set(arrow, 'EdgeColor', 'interp', 'FaceColor', 'interp');
%camlight headlight %might be interesting to uncomment this line
pause(5)
surfaceHandle = rotateAxisTicks('lol','r',10,-0.3,0.5,0.5,1,1,1,0);
pause(5)
camlight headlight
%material(surfaceHandle,'default') %doesn't work
%surfaceHandle1.FaceLighting = 'none' %doesn't work
, which uses that function rotateAxisTicks.m
function surfaceHandle = rotateAxisTicks(str,color,fontsize,zmax,graduSpace,boxHeight,perc,labelNumber,axnumber,thetaInput)
%https://stackoverflow.com/questions/9843048/matlab-how-to-plot-a-text-in-3d
%zmax : give it a negative value to not overlap the axis
%graduSpace : space between each graduation, within the projected on [0,1] axis if axis = x||y, OR local (not yet projected on x,y) axis !!
%boxHeight : width of the boxes depend on how much the axis graduations are refined, so height shouldn't depend on graduSpace
%perc : if perc = 1 (100%), then the labels are all sticked together with no space inbetween
%labelNumber : the first tick to be displayed is actually associated to the second graduation (0 can't get several labels)
%axnumber : out of nbParams, 1 for x, 2 for y, then, from closest to x, to closest to y : 3 to nbParams.
%thetaInput : (angle around z, from x to the axis) has to be in degree
%% Seems like there is no way to get rid of the black contouring...
hFigure = figure(1000);
set(hFigure,'Color', 'w', ... % Create a figure window
'MenuBar', 'none', ...
'ToolBar', 'none');
hText = uicontrol('Parent', hFigure, ... % Create a text object
'Style', 'text', ...
'String', str, ...
'BackgroundColor', 'w', ...
'ForegroundColor', color, ...
'FontSize', fontsize, ...
'FontWeight', 'normal');
set([hText hFigure], 'Pos', get(hText, 'Extent')); %# Adjust the sizes of the
%# text and figure
imageData = getframe(hFigure); %# Save the figure as an image frame
delete(hFigure);
textImage = imageData.cdata; %# Get the RGB image of the text
%% MAKE THE X,Y,Z (text) REVERSE DEPENDING ON AZIMUT VALUE (launch a fig and see on the bottom in real time the azimut value)
% X or Y or Z(1,1) = _______
% * |
% | |
% |_______|
% X or Y or Z(1,2) = _______
% | *
% | |
% |_______|
% X or Y or Z(2,1) = _______
% | |
% | |
% x_______|
% X or Y or Z(2,2) = _______
% | |
% | |
% |_______x
if axnumber == 2 %axis = y
X = [0 0; 0 0];
Y = [0 perc*graduSpace; 0 perc*graduSpace] + labelNumber*graduSpace - perc*graduSpace/2;
%(graduSpace/2)/2 to center under the graduation, (1-perc)/2) to
%additionally shift a bit so that the perc% of graduSpace stay centered
%under the graduation
else %I assume axis = x, that I might later rotate if it's not actually x
X = [0 perc*graduSpace; 0 perc*graduSpace] + labelNumber*graduSpace - perc*graduSpace/2; %+labelNumber*((graduSpace/2)+((1-perc)/2)*graduSpace)
Y = [0 0; 0 0];
end
Z = [zmax zmax; zmax-boxHeight zmax-boxHeight];
surfaceHandle = surf(X, Y, Z, 'FaceColor', 'texturemap', 'CData', textImage);
if axnumber > 2
rotate(surfaceHandle, [0 0 1], thetaInput,[0 0 0]);
end
end
as well as that function arrow3D.m :
function arrowHandle = arrow3D(pos, deltaValues, colorCode, stemRatio, cylRad, radRatioCone)
% arrowHandle = arrow3D(pos, deltaValues, colorCode, stemRatio) %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% Used to plot a single 3D arrow with a cylindrical stem and cone arrowhead
% pos = [X,Y,Z] - spatial location of the starting point of the arrow (end of stem)
% deltaValues = [QX,QY,QZ] - delta parameters denoting the magnitude of the arrow along the x,y,z-axes (relative to 'pos')
% colorCode - Color parameters as per the 'surf' command. For example, 'r', 'red', [1 0 0] are all examples of a red-colored arrow
% stemRatio - The ratio of the length of the stem in proportion to the arrowhead. For example, a call of:
% arrow3D([0,0,0], [100,0,0] , 'r', 0.82) will produce a red arrow of magnitude 100, with the arrowstem spanning a distance
% of 82 (note 0.82 ratio of length 100) while the arrowhead (cone) spans 18.
%
% Example:
% arrow3D([0,0,0], [4,3,7]); %---- arrow with default parameters
% axis equal;
%
% Author: Shawn Arseneau
% Created: September 14, 2006
% Updated: September 18, 2006
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
if nargin<2 || nargin>6
error('Incorrect number of inputs to arrow3D');
end
if numel(pos)~=3 || numel(deltaValues)~=3
error('pos and/or deltaValues is incorrect dimensions (should be three)');
end
if nargin<3
colorCode = 'interp';
end
if nargin<4
stemRatio = 0.75;
end
X = pos(1); %---- with this notation, there is no need to transpose if the user has chosen a row vs col vector
Y = pos(2);
Z = pos(3);
[sphi, stheta, srho] = cart2sph(deltaValues(1), deltaValues(2), deltaValues(3));
%******************************************* CYLINDER == STEM *********************************************
%cylinderRadius = 0.05*srho;
cylinderRadius = cylRad;
cylinderLength = srho*stemRatio;
[CX,CY,CZ] = cylinder(cylinderRadius);
CZ = CZ.*cylinderLength; %---- lengthen
%----- ROTATE CYLINDER
[row, col] = size(CX); %---- initial rotation to coincide with X-axis
newEll = rotatePoints([0 0 -1], [CX(:), CY(:), CZ(:)]); %CX(:) actually reshape the 2xN matrices in a 2N vert vector, by vertically concatenating each column
CX = reshape(newEll(:,1), row, col);
CY = reshape(newEll(:,2), row, col);
CZ = reshape(newEll(:,3), row, col);
[row, col] = size(CX);
newEll = rotatePoints(deltaValues, [CX(:), CY(:), CZ(:)]);
stemX = reshape(newEll(:,1), row, col);
stemY = reshape(newEll(:,2), row, col);
stemZ = reshape(newEll(:,3), row, col);
%----- TRANSLATE CYLINDER
stemX = stemX + X;
stemY = stemY + Y;
stemZ = stemZ + Z;
%******************************************* CONE == ARROWHEAD *********************************************
coneLength = srho*(1-stemRatio);
coneRadius = cylinderRadius*radRatioCone;
incr = 100; %---- Steps of cone increments
coneincr = coneRadius/incr;
[coneX, coneY, coneZ] = cylinder(cylinderRadius*2:-coneincr:0); %---------- CONE
coneZ = coneZ.*coneLength;
%----- ROTATE CONE
[row, col] = size(coneX);
newEll = rotatePoints([0 0 -1], [coneX(:), coneY(:), coneZ(:)]);
coneX = reshape(newEll(:,1), row, col);
coneY = reshape(newEll(:,2), row, col);
coneZ = reshape(newEll(:,3), row, col);
newEll = rotatePoints(deltaValues, [coneX(:), coneY(:), coneZ(:)]);
headX = reshape(newEll(:,1), row, col);
headY = reshape(newEll(:,2), row, col);
headZ = reshape(newEll(:,3), row, col);
%---- TRANSLATE CONE
V = [0, 0, srho*stemRatio]; %---- centerline for cylinder: the multiplier is to set the cone 'on the rim' of the cylinder
Vp = rotatePoints([0 0 -1], V);
Vp = rotatePoints(deltaValues, Vp);
headX = headX + Vp(1) + X;
headY = headY + Vp(2) + Y;
headZ = headZ + Vp(3) + Z;
%************************************************************************************************************
hStem = surf(stemX, stemY, stemZ, 'FaceColor', colorCode, 'EdgeColor', 'none');
hold on
hBottStem = fill3(stemX(1,:),stemY(1,:),stemZ(1,:), colorCode, 'EdgeColor', 'none');
hold on
hHead = surf(headX, headY, headZ, 'FaceColor', colorCode, 'EdgeColor', 'none');
hold on
hBottCone = fill3(headX(1,:),headY(1,:),headZ(1,:), colorCode, 'EdgeColor', 'none');
if nargout==1
arrowHandle = [hStem, hBottStem, hHead, hBottCone];
end
which itself uses that function rotatePoints.m :
function rotatedData = rotatePoints(alignmentVector, originalData)
% rotatedData = rotatePoints(alignmentVector, originalData) %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% Rotate the 'originalData' in the form of Nx2 or Nx3 about the origin by aligning the x-axis with the alignment vector
%
% Rdata = rotatePoints([1,2,-1], [Xpts(:), Ypts(:), Zpts(:)]) - rotate the (X,Y,Z)pts in 3D with respect to the vector [1,2,-1]
%
% Rotating using spherical components can be done by first converting using [dX,dY,dZ] = cart2sph(theta, phi, rho); alignmentVector = [dX,dY,dZ];
%
% Example:
% %% Rotate the point [3,4,-7] with respect to the following:
% %%%% Original associated vector is always [1,0,0]
% %%%% Calculate the appropriate rotation requested with respect to the x-axis. For example, if only a rotation about the z-axis is
% %%%% sought, alignmentVector = [2,1,0] %% Note that the z-component is zero
% rotData = rotatePoints(alignmentVector, [3,4,-7]);
%
% Author: Shawn Arseneau
% Created: Feb.2, 2006
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
alignmentDim = numel(alignmentVector); %number of elements in a matrix
DOF = size(originalData,2); %---- DOF = Degrees of Freedom (i.e. 2 for two dimensional and 3 for three dimensional data)
if alignmentDim~=DOF
error('Alignment vector does not agree with originalData dimensions');
end
if DOF<2 || DOF>3
error('rotatePoints only does rotation in two or three dimensions');
end
if DOF==2 % 2D rotation...
[rad_theta, rho] = cart2pol(alignmentVector(1), alignmentVector(2));
deg_theta = -1 * rad_theta * (180/pi);
ctheta = cosd(deg_theta); stheta = sind(deg_theta);
Rmatrix = [ctheta, -1.*stheta;...
stheta, ctheta];
rotatedData = originalData*Rmatrix;
%assumption: rotate all the datas from the original base to the
%base where the original x becomes alignmentVector
else % 3D rotation...
[rad_theta, rad_phi, rho] = cart2sph(alignmentVector(1), alignmentVector(2), alignmentVector(3));
rad_theta = rad_theta * -1;
deg_theta = rad_theta * (180/pi);
deg_phi = rad_phi * (180/pi);
ctheta = cosd(deg_theta); stheta = sind(deg_theta); %MM : is it more accurate??
Rz = [ctheta, -1.*stheta, 0;...
stheta, ctheta, 0;...
0, 0, 1]; %% First rotate as per theta around the Z axis
rotatedData = originalData*Rz;
[rotX, rotY, rotZ] = sph2cart(-1* (rad_theta+(pi/2)), 0, 1); %% Second rotation corresponding to phi
%assuming alignmentVector is the x for the new base, then the
%hereabove argument corresponds to the y (z inversed)
%the hereabove output = newX(in base 0) vectorial product -z(in base0)
rotationAxis = [rotX, rotY, rotZ];
u = rotationAxis(:)/norm(rotationAxis); %% Code extract from rotate.m from MATLAB
cosPhi = cosd(deg_phi);
sinPhi = sind(deg_phi);
invCosPhi = 1 - cosPhi;
x = u(1);
y = u(2);
z = u(3);
Rmatrix = [cosPhi+x^2*invCosPhi x*y*invCosPhi-z*sinPhi x*z*invCosPhi+y*sinPhi; ...
x*y*invCosPhi+z*sinPhi cosPhi+y^2*invCosPhi y*z*invCosPhi-x*sinPhi; ...
x*z*invCosPhi-y*sinPhi y*z*invCosPhi+x*sinPhi cosPhi+z^2*invCosPhi]';
rotatedData = rotatedData*Rmatrix;
end
I end up getting:
while I would like to conserve both intermediate plots containing the interp arrow:
and the text on flashy white background:
So there are actually two questions:
1) Why calling my text tick disables the interp effect (varying color from blue to yellow) ?
2) How can I keep the camlight without enlightening my tick box? (i.e while keeping its background white)
Basically one should only need to look at the two first snippets, the later 2 ones are useless for my issue.
By thanking you a lot!
Place it in another axes
As I said in comment, you have 2 graphic objects in the same axes which have to interpret their CData in a completely different manner.
The first options I looked for was to modify one of the arrow3d or rotateAxisTicks so their graphic objects would be "compatible" (in the way the color data are interpolated on an axes), but it would be quite intensive and the aspect of the 3d text would have to be constantly monitored/adjusted for any other change in the figure.
So the easiest option is a classic MATLAB hack ... place your graphic objects in different containers (different axes), then superimpose them on a figure, and match some properties (limits, view, etc ...) so they appear to be only one.
Here it goes:
%% Draw your main arrow in the main figure
mainfig = figure ;
ax1 = axes ;
arrow = arrow3D([0 0 0], [1 1 1], 'r', 0.8, 0.2, 1.5);
set(arrow, 'EdgeColor', 'interp', 'FaceColor', 'interp');
camlight headlight
%% Draw your text in a temporary figure
tempfig = figure ;
ax2 = axes ;
surfaceHandle = rotateAxisTicks('lol','r',10,-0.3,0.5,0.5,1,1,1,0);
camlight headlight
%material(surfaceHandle,'default') %doesn't work
%surfaceHandle1.FaceLighting = 'none' %doesn't work
%% Prepare and set matching limits
xl = [ax1.XLim ; ax2.XLim] ;
xl = [min(xl(:,1)) , max(xl(:,2))] ;
yl = [ax1.YLim ; ax2.YLim] ;
yl = [min(yl(:,1)) , max(yl(:,2))] ;
zl = [ax1.ZLim ; ax2.ZLim] ;
zl = [min(zl(:,1)) , max(zl(:,2))] ;
hax = [ax1;ax2] ;
set(hax,'XLim',xl,'YLim',yl,'ZLim',zl)
% Adjust the view to be sure
ax2.View = ax1.View ;
%% Remove secondary axes background, then move it to main figure
ax2.Visible = 'off' ;
ax2.Parent = mainfig ;
delete(tempfig)
%% link the view between axes
hl = linkprop( hax , 'View' ) ;
% or link even more properties at once
% hl = linkprop( hax , 'View' , 'XLim','YLim','ZLim') ;
Which gives you:
note: Your 3d arrow is also made up of 2 different graphic objects (2x surf and 2x patch). The 2 patches are not rendered when you set the interp mode. You should modify the arrow3d function to either (a) changes the patch objects to surf so everything is the same type and compatible, or (b) remove them completely from the function (if they are not rendered they are only annoying ... triggering warnings everywhere).
edit
And here is the modified code for arrow3d.m. I changed it so the output is now only one surface object, easier to assign properties and no danger of mismatch between patch and surf. I also simplified it, removed a few bits that were not necessary, and reduced the total number of points necessary for the surface.
With this you get the bottom of the stem and the under-cone:
function arrowHandle = arrow3D(pos, deltaValues, colorCode, stemRatio, cylRad )
% arrowHandle = arrow3D(pos, deltaValues, colorCode, stemRatio) %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% Used to plot a single 3D arrow with a cylindrical stem and cone arrowhead
% pos = [X,Y,Z] - spatial location of the starting point of the arrow (end of stem)
% deltaValues = [QX,QY,QZ] - delta parameters denoting the magnitude of the arrow along the x,y,z-axes (relative to 'pos')
% colorCode - Color parameters as per the 'surf' command. For example, 'r', 'red', [1 0 0] are all examples of a red-colored arrow
% stemRatio - The ratio of the length of the stem in proportion to the arrowhead. For example, a call of:
% arrow3D([0,0,0], [100,0,0] , 'r', 0.82) will produce a red arrow of magnitude 100, with the arrowstem spanning a distance
% of 82 (note 0.82 ratio of length 100) while the arrowhead (cone) spans 18.
%
% Example:
% arrow3D([0,0,0], [4,3,7]); %---- arrow with default parameters
% axis equal;
%
% Author: Shawn Arseneau
% Created: September 14, 2006
% Updated: September 18, 2006
%
% Updated: December 20, 2018
% Tlab - refactored to have only one surface object as ouput
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
if nargin<2 || nargin>6
error('Incorrect number of inputs to arrow3D');
end
if numel(pos)~=3 || numel(deltaValues)~=3
error('pos and/or deltaValues is incorrect dimensions (should be three)');
end
if nargin<3
colorCode = 'interp';
end
if nargin<4
stemRatio = 0.75;
end
Ncol = 21 ; % default number of column for the "cylinder.m" function
X = pos(1); %---- with this notation, there is no need to transpose if the user has chosen a row vs col vector
Y = pos(2);
Z = pos(3);
[~, ~, srho] = cart2sph(deltaValues(1), deltaValues(2), deltaValues(3));
%******************************************* CYLINDER == STEM *********************************************
cylinderRadius = cylRad;
cylinderLength = srho*stemRatio;
[CX,CY,CZ] = cylinder(cylinderRadius,Ncol-1);
CZ = CZ.*cylinderLength; %---- lengthen
%******************************************* CONE == ARROWHEAD *********************************************
coneLength = srho*(1-stemRatio);
[coneX, coneY, coneZ] = cylinder([cylinderRadius*2 0],Ncol-1); %---------- CONE
coneZ = coneZ.*coneLength;
% Translate cone on top of the stem cylinder
coneZ = coneZ + cylinderLength ;
% now close the bottom and add the cone to the stem cylinder surface
bottom = zeros(1,Ncol) ;
CX = [ bottom ; CX ; coneX ] ;
CY = [ bottom ; CY ; coneY ] ;
CZ = [ bottom ; CZ ; coneZ ] ;
Nrow = size(CX,1);
%----- ROTATE
%---- initial rotation to coincide with X-axis
newEll = rotatePoints([0 0 -1], [CX(:), CY(:), CZ(:)]); %CX(:) actually reshape the 2xN matrices in a 2N vert vector, by vertically concatenating each column
CX = reshape(newEll(:,1), Nrow, Ncol);
CY = reshape(newEll(:,2), Nrow, Ncol);
CZ = reshape(newEll(:,3), Nrow, Ncol);
newEll = rotatePoints(deltaValues, [CX(:), CY(:), CZ(:)]);
stemX = reshape(newEll(:,1), Nrow, Ncol);
stemY = reshape(newEll(:,2), Nrow, Ncol);
stemZ = reshape(newEll(:,3), Nrow, Ncol);
%----- TRANSLATE
stemX = stemX + X;
stemY = stemY + Y;
stemZ = stemZ + Z;
%----- DISPLAY
hStem = surf(stemX, stemY, stemZ, 'FaceColor', colorCode, 'EdgeColor', 'none');
%----- DISPLAY
if nargout==1
arrowHandle = hStem ;
end

Create 3D effect of 2D line plot matlab [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 4 years ago.
Improve this question
Question, I have a line data. However I wanted to be plotted with a 3D effect, which you can also choose as an option, for instance, within Office Excel/Powerpoint.
Currently I have the code below which kind of mimmics the effect (simply putting a lot of plots after each other):
Excel:
Matlab:
Matlab code:
x = 1:10;
y = rand(1, 10);
z = rand(1, 10);
figure;
hold on;
for i = 1:20
hFill = fill3(i*0.01*ones(1, 12), x([1 1:end end]), [0 y 0], 'b', 'FaceAlpha', 0.5);
hFill = fill3((i*0.01*(ones(1, 12)))+2, x([1 1:end end]), [0 z 0], 'g', 'FaceAlpha', 0.5);
end
grid on;
xlim([0 10]);
view(3);
You could probably construct a closed surface in one single graphic object to represent your 3D curve, however I don't have time to work that out completely so I'll go the lazy way:
Instead of stacking multiple fill objects to give a 3d feeling, I build each curve with 3 objects:
A top surface, with the desired thickness
2 patch objects to close the sides
Here goes:
%% Sample data
rng(12)
x = 1:10;
y = rand(1, 10) + .5 ;
z = rand(1, 10) + .5 ;
%% Parameters
alphaTop = .5 ; % alpha value of the top of the surface
alphaSide = .5 ; % alpha value of the sides
thickness = .5 ; % thickness of each curve
separation = 1 ; % separation of each curve
colors = {'b';'r'} ; % color for each curve
%% prepare patch coordinates
xp = x([1 1:end end 1]) ;
yp1 = [0 y 0 0] ;
yp2 = [0 z 0 0] ;
zp1 = zeros(size(yp1)) ;
zp2 = zeros(size(yp2)) + 1 ;
%% Prepare surface coordinates
xs = [ xp; xp] ;
ys1 = [yp1;yp1] ;
ys2 = [yp2;yp2] ;
zs1 = zeros(size(xs)) ;
zs1(2,:) = zs1(2,:) + thickness ;
zs2 = zs1 + separation ;
%% Display
figure
hold on
% plot the sides (one patch on each side of each curve)
hp11 = patch(zp1 ,xp,yp1, colors{1} , 'FaceAlpha', alphaSide) ;
hp12 = patch(zp1+thickness,xp,yp1, colors{1} , 'FaceAlpha', alphaSide) ;
hp21 = patch(zp2 ,xp,yp2, colors{2} , 'FaceAlpha', alphaSide) ;
hp22 = patch(zp2+thickness,xp,yp2, colors{2} , 'FaceAlpha', alphaSide) ;
% plot the top surfaces
hs1 = surf(zs1,xs,ys1, 'FaceColor',colors{1},'FaceAlpha',alphaTop) ;
hs2 = surf(zs2,xs,ys2, 'FaceColor',colors{2},'FaceAlpha',alphaTop) ;
% refine plot
xlim([0 10]);ylim([0 10]); view(3);
xlabel('X') ; ylabel('Y') ; zlabel('Z') ;
Which yields:
Once this is built, you can regroup the graphic handles to group common properties assignments. For example:
%% Optional (modify common properties in group)
% regroup graphic handles for easy common property assignment
hg1 = [hp11;hp12;hs1] ;
hg2 = [hp21;hp22;hs2] ;
% set properties in group
set(hg1,'EdgeColor',colors{1},'FaceAlpha',0.2) ;
set(hg2,'EdgeColor',colors{2},'FaceAlpha',0.2) ;
To give your curves a nice transparent mesh style:
Ultimately, if you plan to apply this method to many curves, you should either package it in a function, or at least build your curves with a loop. It should be easy to convert as long as the parameters for each curve are in an array you can index into (like I did for the colors).

Radon Transform Line Detection

I'm trying to detect lines in a grayscale image. For that purpose, I'm using Radon transform in MATLAB. An example of my m-file is like below. I can detect multiple lines using this code. I also draw lines using shift and rotation properties for lines. However, I didn't understand how to get the start and end points of the detecting lines after getting rho and theta values.
It is easy for Hough transform since there is a function called houghlines() that returns the list of the lines for the given peaks. Is there any function that i can use for Radon transform similar to this function?
% Radon transform line detection algorithm
clear all; close all;
% Determine the path of the input image
str_inputimg = '3_lines.png' ;
% Read input image
I = imread(str_inputimg) ;
% If the input image is RGB or indexed color, convert it to grayscale
img_colortype = getfield(imfinfo(str_inputimg), 'ColorType') ;
switch img_colortype
case 'truecolor'
I = rgb2gray(I) ;
case 'indexedcolor'
I = ind2gray(I) ;
end
figure;
subplot(2,2,1) ;
imshow(I) ;
title('Original Image') ;
% Convert image to black white
%BW = edge(I,'Sobel');
BW=im2bw(I,0.25) ;
subplot(2,2,2) ;
imshow(BW);
title('BW Image') ;
% Radon transform
% Angle projections
theta = [0:179]' ;
[R, rho] = radon(BW, theta) ;
subplot(2,2,3) ;
imshow(R, [], 'XData', theta, 'YData', rho, 'InitialMagnification', 'fit');
xlabel('\theta'), ylabel('\rho');
axis on, axis normal, hold on;
% Detect the peaks of transform output
% Threshold value for peak detection
threshold_val = ceil(0.3*max(R(:))) ;
% Maximum nof peaks to identify in the image
max_nofpeaks = 5 ;
max_indexes = find(R(:)>threshold_val) ;
max_values = R(max_indexes) ;
[sorted_max, temp_indexes] = sort(max_values, 'descend') ;
sorted_indexes = max_indexes(temp_indexes) ;
% Get the first highest peaks for the sorted array
if (length(sorted_max) <= max_nofpeaks)
peak_values = sorted_max(1:end) ;
peak_indexes = sorted_indexes(1:end) ;
else
peak_values = sorted_max(1:max_nofpeaks) ;
peak_indexes = sorted_indexes(1:max_nofpeaks) ;
end
[y, x] = ind2sub(size(R), peak_indexes ) ;
peaks = [rho(y) theta(x)] ;
plot(peaks(:,2), peaks(:,1), 's', 'color','white');
title('Radon Transform & Peaks') ;
% Detected lines on the image
subplot(2,2,4), imshow(I), title('Detected lines'), hold on
x_center = floor(size(I, 2)/2) ;
y_center = floor(size(I, 1)/2) ;
for p=1:length(peaks)
x_1 = [-x_center, x_center] ;
y_1 = [0, 0] ;
% Shift at first
x_1_shifted = x_1 ;
y_1_shifted = [y_1(1)-peaks(p,1), y_1(2)-peaks(p,1)] ;
% Rotate
peaks(p,2) = 90 - peaks(p,2) ;
t=peaks(p,2)*pi/180;
rotation_mat = [ cos(t) -sin(t) ; sin(t) cos(t) ] ;
x_y_rotated = rotation_mat*[x_1_shifted; y_1_shifted] ;
x_rotated = x_y_rotated(1,:) ;
y_rotated = x_y_rotated(2,:) ;
plot( x_rotated+x_center, y_rotated+y_center, 'b', 'linewidth', 2 );
end
hold off;
There's a suggestion at math.SE which might help. Then there's a rather complicated-looking research paper "Sharp endpoint estimates for the X-ray transform and the Radon
transform in finite fields", which appears just to show certain bounds on estimation accuracy.
From skimming other papers, it appears that it's a nontrivial problem. I suspect it may be simpler (if less accurate) to use some adaptation of a Sobel-operation to identify high gradient points along the discovered line, and claim those as endpoints.

Change color of 2D plot line depending on 3rd value

I have a data set that looks like this
140400 70.7850 1
140401 70.7923 2
140402 70.7993 3
140403 70.8067 4
140404 70.8139 5
140405 70.8212 3
Where the first column corresponds to time (one second intervals between data points) and will be on the x axis, the second column corresponds with distance and will be on the y axis. The third column is a number (one through five) that is a qualification of the movement.
I want to make a plot that changes the color of the line between two points depending on what the number of the previous data point was. For example, I want the line to be red between the first and second data points because the qualification value was 1.
I've seen a lot of posts about making a sliding scale of colors depending on an intensity value, but I just want 5 colors: (red, orange, yellow, green, and blue) respectively.
I tried doing something like this:
plot(x,y,{'r','o','y','g','b'})
But with no luck.
Any ideas of how to approach this? Without looping if possible.
You can also do it with a trick which works with Matlab version anterior to 2014b (as far back as 2009a at least).
However, is will never be as simple as you expected (unless you write a wrapper for one of the solution here you can forget about plot(x,y,{'r','o','y','g','b'})).
The trick is to use a surface instead of a line object. Surfaces benefit from their CData properties and a lot of useful features to exploit color maps and texture.
Matlab surf does not handle 1D data, it needs a matrix as input so we are going to give it by just duplicating each coordinate set (for example xx=[x,x]).
Don't worry though, the surface will stay as thin as a line, so the end result is not ugly.
%% // your data
M=[140400 70.7850 1
140401 70.7923 2
140402 70.7993 3
140403 70.8067 4
140404 70.8139 5
140405 70.8212 3];
x = M(:,1) ; %// extract "X" column
y = M(:,2) ; %// same for "Y"
c = M(:,3) ; %// extract color index for the custom colormap
%% // define your custom colormap
custom_colormap = [
1 0 0 ; ... %// red
1 .5 0 ; ... %// orange
1 1 0 ; ... %// yellow
0 1 0 ; ... %// green
0 0 1 ; ... %// blue
] ;
%% // Prepare matrix data
xx=[x x]; %// create a 2D matrix based on "X" column
yy=[y y]; %// same for Y
zz=zeros(size(xx)); %// everything in the Z=0 plane
cc =[c c] ; %// matrix for "CData"
%// draw the surface (actually a line)
hs=surf(xx,yy,zz,cc,'EdgeColor','interp','FaceColor','none','Marker','o') ;
colormap(custom_colormap) ; %// assign the colormap
shading flat %// so each line segment has a plain color
view(2) %// view(0,90) %// set view in X-Y plane
colorbar
will get you:
As an example of a more general case:
x=linspace(0,2*pi);
y=sin(x) ;
xx=[x;x];
yy=[y;y];
zz=zeros(size(xx));
hs=surf(xx,yy,zz,yy,'EdgeColor','interp') %// color binded to "y" values
colormap('hsv')
view(2) %// view(0,90)
will give you a sine wave with the color associated to the y value:
Do you have Matlab R2014b or higher?
Then you could use some undocumented features introduced by Yair Altman:
n = 100;
x = linspace(-10,10,n); y = x.^2;
p = plot(x,y,'r', 'LineWidth',5);
%// modified jet-colormap
cd = [uint8(jet(n)*255) uint8(ones(n,1))].' %'
drawnow
set(p.Edge, 'ColorBinding','interpolated', 'ColorData',cd)
My desired effect was achieved below (simplified):
indices(1).index = find( data( 1 : end - 1, 3) == 1);
indices(1).color = [1 0 0];
indices(2).index = find( data( 1 : end - 1, 3) == 2 | ...
data( 1 : end - 1, 3) == 3);
indices(2).color = [1 1 0];
indices(3).index = find( data( 1 : end - 1, 3) == 4 | ...
data( 1 : end - 1, 3) == 5);
indices(3).color = [0 1 0];
indices(4).index = find( data( 1 : end - 1, 3) == 10);
indices(4).color = [0 0 0];
indices(5).index = find( data( 1 : end - 1, 3) == 15);
indices(5).color = [0 0 1];
% Loop through the locations of the values and plot their data points
% together (This will save time vs. plotting each line segment
% individually.)
for iii = 1 : size(indices,2)
% Store locations of the value we are looking to plot
curindex = indices(iii).index;
% Get color that corresponds to that value
color = indices(iii).color;
% Create X and Y that will go into plot, This will make the line
% segment from P1 to P2 have the color that corresponds with P1
x = [data(curindex, 1), data(curindex + 1, 1)]';
y = [data(curindex, 2), data(curindex + 1, 2)]';
% Plot the line segments
hold on
plot(x,y,'Color',color,'LineWidth',lineWidth1)
end
When the result figure of two variables plotted is a circle, will be necessary to add the time in z axes.
For example the figure of induction machine rotor velocity vs electric torque in one laboratory test is: 2d plot figure
In the last figure the direction of the time point plotting could be clockwise or counter clockwise. For the last reason will be added time in z axis.
% Wr vs Te
x = logsout.getElement( 'Wr' ).Values.Data;
y = logsout.getElement( '<Te>' ).Values.Data;
z = logsout.getElement( '<Te>' ).Values.Time;
% % adapt variables for use surf function
xx = zeros( length( x ) ,2 );
yy = zeros( length( y ) ,2 );
zz = zeros( length( z ) ,2 );
xx (:,1) = x; xx (:,2) = x;
yy (:,1) = y; yy (:,2) = y;
zz (:,1) = z; zz (:,2) = z;
% % figure(1) 2D plot
figure (1)
hs = surf(xx,yy,zz,yy,'EdgeColor','interp') %// color binded to "y" values
colormap('hsv')
view(2)
% %
figure(2)
hs = surf(xx,yy,zz,yy,'EdgeColor','interp') %// color binded to "y" values
colormap('hsv')
view(3)
Finally we can view the 3d form and detect that counterwise is the real direction of the time plotting is: 3d plot
Scatter can plot the color according to the value and shows the colormap of the range of values. It's hard to interpolate the color though if you want continuous curves.
Try:
figure
i = 1:20;
t = 1:20;
c = rand(1, 20) * 10;
scatter(i, t, [], c, 's', 'filled')
colormap(jet)
The figure looks like

3d Ring in Matlab using Patch

I am trying to make a 3d ring in a matlab. Following is something similar to what I am upto:
t = linspace(0,2*pi);
rin = 0.1;
rout = 0.25;
xin = 0.5 + rin*cos(t);% 0.5 is the center of the ring
xout = 0.5 + rout*cos(t);
yin = 0.5 + rin*sin(t);
yout = 0.5 + rout*sin(t);
% Make patch
hp = patch([xout,xin],[yout,yin],'g','linestyle','none','facealpha',0.25);
Now, I want to extend this to the 3d case. I wanted to give height to this ring. But when I try to add a vector [z1,z2] where
z1=0.5*ones(size(xin));
z2=z1;
I have tried different combination of Z1 and Z2 still I am unable to find the solution.
I tend to keep the usage of patch for 2D surfaces of object which are really faceted and if you need control over the properties of every face, otherwise for volumes like you want surf is a much easier object to handle.
The example below shows how to do it with one single surface, but is easily adaptable if you really need to use patch.
To explain step by step I start with the patch you created. I calculate the coordinates of the center line (called ring in the code), then display it (with the starting point highlighted)
%% // Ring properties
ring.x0 = 0.5 ; %// Center of ring
ring.y0 = 0.5 ; %// Center of ring
ring.radius = (0.25+0.1)/2 ; %// Radius of core circle profile
ring.nDiv = 37 ; %// number of divisions for the core circle profile
ring.theta = linspace(0,2*pi,ring.nDiv) ;
ring.X = cos(ring.theta) * ring.radius + ring.x0 ;
ring.Y = sin(ring.theta) * ring.radius + ring.y0 ;
%// plot (optional, just for intermediate visualisation)
hold on ; plot(ring.X,ring.Y) ; plot(ring.X(1),ring.Y(1),'ok')
view(18,72) ; xlabel('X') ; ylabel('Y') ; zlabel('Z') ;
which render:
Then I create a basic cross section:
%% // Create a base SQUARE cross section
Npts = 4 ;
cs.width = 0.25-0.1 ; %// width of each cross section square
cs.height = 0.25 ; %// height of each cross section square
%// first cross section is the the XZ plane
csY0 = zeros(1,Npts) ; %// will be used as base for rotating cross sections
csX = [-cs.width/2 cs.width/2 cs.width/2 -cs.width/2 ] ;
csZ = [-cs.height/2 -cs.height/2 cs.height/2 cs.height/2] ;
This defined a basic square hanging in space around the origin, I'll place the first one in position just to illustrate:
%% // plot (optional, just for intermediate visualisation)
hp0 = patch(csX+ring.X(1),csY0+ring.Y(1),csZ,'r','FaceAlpha',0.5) ;
view(164,38)
Which renders:
Now we just need to replicate this cross section wrapped around the master ring:
%% Generate coordinates for each cross section and merge them
nCS = length(ring.X) ; %// number of cross sections composing the surface
%// pre-allocation is always good
X = zeros( nCS , Npts ) ;
Y = zeros( nCS , Npts ) ;
Z = zeros( nCS , Npts ) ;
for ip = 1:nCS
%// rotate the cross section (around Z axis, around origin)
Rmat = [ cos(ring.theta(ip)) -sin(ring.theta(ip)) ; ...
sin(ring.theta(ip)) cos(ring.theta(ip)) ] ;
csTemp = Rmat * [csX ; csY0] ;
%// translate the coordinates of cross section to final position and store with others
X(ip,:) = csTemp(1,:) + ring.X(ip) ;
Y(ip,:) = csTemp(2,:) + ring.Y(ip) ;
Z(ip,:) = csZ ;
end
Now you have in X,Y and Z the coordinates of points all around the profile you defined, ready to be plotted in one graphical object:
%% // Plot the final surface
hs = surf(X,Y,Z) ;
set(hs,'FaceColor',[.7 .7 .7],'FaceAlpha',0.5,'EdgeAlpha',0.2)
view(155,26)
Which renders:
The 2 nice points about this method are:
Only one graphic object to handle (still versatile though, the CData allows many possibilities)
The cross section can be anything, just define it once, and repeat the method.
To illustrate point 2 above, just replace the paragraph of code %% // Create a base square cross section by this circular cross section:
%% // Create a base CIRCULAR cross section
cs.Ndiv = 13 ; %
cs.radius = (0.25-0.1)/2 ; %// Radius of each cross section circle
cs.rout = 0.25;
cs.theta = linspace(0,2*pi,cs.Ndiv) ;
Npts = length(cs.theta) ;
%// first cross section is the the XZ plane
csY0 = zeros(1,Npts) ; %// will be used as base for rotating cross sections
csX = sin(cs.theta) * cs.radius ;
csZ = cos(cs.theta) * cs.radius ;
The rest of the code is the same, you'll obtain your doughnut:
I included that anyway because that was my first answer, until I realized from the comment that you wanted a cylinder!
Duplicating your annulus and adding two cylinders i get:
%% Config
t = linspace(0,2*pi);
rin = 0.1;
rout = 0.25;
center = [1, 0.5];
xin = rin*cos(t);
xout = rout*cos(t);
yin = rin*sin(t);
yout = rout*sin(t);
z1 = 0;
z2 = 0.24;
%% Plot
clf;
hold on;
bottom = patch(center(1)+[xout,xin], ...
center(2)+[yout,yin], ...
z1*ones(1,2*length(xout)),'');
top = patch(center(1)+[xout,xin], ...
center(2)+[yout,yin], ...
z2*ones(1,2*length(xout)),'');
[X,Y,Z] = cylinder(1,length(xin));
outer = surf(rout*X+center(1), ...
rout*Y+center(2), ...
Z*(z2-z1)+z1);
inner = surf(rin*X+center(1), ...
rin*Y+center(2), ...
Z*(z2-z1)+z1);
set([bottom, top, outer, inner], ...
'FaceColor', [0 1 0], ...
'FaceAlpha', 0.99, ...
'linestyle', 'none', ...
'SpecularStrength', 0.7);
light('Position',[1 3 2]);
light('Position',[-3 -1 3]);
axis vis3d; axis equal; view(3);