I'm trying to create a plot on Matlab with multiple axis breaks (so something like the following):
I've tried using things like breakyaxis and breakaxis from the Matlab File Exchange, but those only allow for one break, not multiple.
Is there any way to implement this?
The NaN (Not a Number) values can be an annoying thing but also a convenient one in some case.
When you plot data, Matlab will leave a blank in place of every data point which has no value (the NaN). So the principle is to insert these NaN between your datasets and tell Matlab to plot the whole lot. Matlab will leave a blank automatically everywhere there is a NaN.
Here is an example, since you didn't supply sample data I first have to define 3 short data sets resembling the ones you have in your figure:
%% // sample data sets
yf = #(x) 2*x+40+randi(7,size(x)) ;
x1 = 57:61 ; y1 = yf(x1) ;
x2 = 72:76 ; y2 = yf(x2) ;
x3 = 80:83 ; y3 = yf(x3) ;
This is an edited answer to take into account the breaks in the Y axis. To be able to call global operations on the datasets I have to regroup them into a cell array or a structure. The struture approach would use loops on the different data sets, while the cell array allow the use of cellfun to compact the code. I chose this approach and use cellfun extensively.
So first step is put all your data sets in a cell array
%% // have to group the data sets in a cell array or structure to implement global operations
xc = { x1 ; x2 ; x3 } ;
yc = { y1 ; y2 ; y3 } ;
Now the heavy part:
%// find the maximum vertical span of the datasets and the total span
maxVal = cellfun(#max,yc) ;
minVal = cellfun(#min,yc) ;
maxYspan = max( maxVal-minVal ) ;
totalSpan = max(maxVal)-min(minVal) ;
%// find a sensible Y value to add between the datasets, not too wide but
%// enough to see a break`
yBreakIncrement = round( totalSpan / 10 ) ; %// adjust that if necessary
yTickIncrement = round( maxYspan /5 ) ; %// adjust that if necessary
%% // rebuild the Y datasets
%// value to substract to each data set to bring them together (including the break space)
setSubstract = [0 ; cumsum( (minVal(2:end)-maxVal(1:end-1))- yBreakIncrement ) ] ;
%// get 3 new data sets brought together
Yall = cellfun(#minus , yc , num2cell(setSubstract) , 'uni',0) ;
%// concatenate the data sets, inserting NaN in the middle
Yall = cellfun( #(a,b) cat(2,a,b) , Yall , repmat({NaN},length(yc),1) , 'uni',0) ;
Yall = cat( 2, Yall{:} ) ;
%// remove the last trailing NaN
Yall(end) = [] ;
%% // Build the Y labels
%// generate ticks that covers each interval
Y_tickpos = cellfun(#colon, num2cell(minVal), repmat({yTickIncrement},length(yc),1) , num2cell(maxVal) , 'uni',0) ;
%// generate the Y labels based the real Y values
Y_labels = cellstr( num2str( cat(2, Y_tickpos{:} ).') ) ; %'// ignore this comment
%// now adjust the actual position
Y_tickpos = cellfun(#minus , Y_tickpos , num2cell(setSubstract) , 'uni',0) ;
Y_tickpos = cat( 2, Y_tickpos{:} ) ;
%% // Build the X labels (and axis)
%// create a continuous index for the X axis
X = 1:length(Yall) ;
X_labels = cellstr( num2str( cat(2, xc{:} ).') ) ; %'// generate the X labels based the X values
X_tickpos = X(~isnan(Yall)) ; %// prepare a vector for the label positions
%% // Display
plot(X,Yall) %// plot as usual
%// Set the labels at the chosen positions
set(gca, 'XTick' , X_tickpos , 'XTickLabel' , X_labels )
set(gca, 'YTick' , Y_tickpos , 'YTickLabel' , Y_labels )
That should give you something like:
Hopefully enough to get you started. Try to adapt the principle to your data.
Related
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
I have a cubic box with the size of, lets say 300 in each direction. I have some data (contains X,Y,Z coordinates) which represent the distribution of nearly 1 million data-points within this box. I want to specify a color to their Number density (its an intensive quantity used to describe the degree of concentration of countable objects in this case data-points). In another word, Using color to illustrate which part is more condensed in terms of data-points rather than the other parts. The index for the color-bar in the final image should represent the percentage of data-points specified with that color.
I have tried to do it by dividing the whole space in cubic box to 1 million smaller cube (each cube has a length of 3 in all direction). By counting the number of particles within those cube, I will know how they distributed in the box and the number of existed data-points within. Then I can specify a color to them which I wasn’t successful in counting and specifying. Any suggestion is appreciated.
%reading the files
[FileName,PathName,FilterIndex] = uigetfile('H:\*.txt','MultiSelect','on');
numfiles = size(FileName,2);%('C:\final1.txt');
j=1;
X=linspace(0,300,100);
for ii = 1:numfiles
FileName{ii}
entirefile = fullfile(PathName,FileName{ii});
a = importdata(entirefile);
x = a(:,2);
y = a(:,3);
z = a(:,4);
%% I DON'T KNOW HOW TO CREAT THIS LOOP TO COUNT FOR THE NUMBER OF PARTICLES WITHIN EACH DEFINED CUBE %%
for jj = 2:size(X,2)
%for kk=1:m
if x(:)<X(jj) & y(:)<X(jj) & z(:)<X(jj)
x;
end
%end
end
h=figure(j);
scatter3(x, y, z, 'filled', 'MarkerSize', 20);
cb = colorbar();
cb.Label.String = 'Probability density estimate';
end
I need to get a similar result like the following image. but I need the percentage of data-point specified by each color. Thanks in advance.
Here is a link to a sampled data.
Here is a way to count the 3D density of a point cloud. Although I am affraid the sample data you provided do not yield the same 3D distribution than on your example image.
To count the density, the approach is broken down in several steps:
Calculate a 2D density in the [X,Y] plane: This counts the number of points laying in each (x,y) bin. However, at this stage this number of point incorporates all the Z column for a given bin.
For each non-empty (x,y) bin, calculate the distribution along the Z column. We now have the number of point falling in each (x,y,z) bin. Counting the density/percentage is simply done by dividing each count by the total number of points.
Now for each non-empty (x,y,z) bin, we identify the linear indices of the points belonging to this bin. We then assign the bin value (color, percentage, or any value associated to this bin) to all the identified points.
display the results.
In code, it goes like this:
%% Import sample data
entirefile = '1565015520323.txt' ;
a = importdata(entirefile);
x = a(:,1);
y = a(:,2);
z = a(:,3);
npt = numel(x) ; % Total Number of Points
%% Define domain and grid parameters
nbins = 100 ;
maxDim = 300 ;
binEdges = linspace(0,maxDim,nbins+1) ;
%% Count density
% we start counting density along in the [X,Y] plane (Z axis aglomerated)
[Nz,binEdges,~,binX,binY] = histcounts2(y,x,binEdges,binEdges) ;
% preallocate 3D containers
N3d = zeros(nbins,nbins,nbins) ; % 3D matrix containing the counts
Npc = zeros(nbins,nbins,nbins) ; % 3D matrix containing the percentages
colorpc = zeros(npt,1) ; % 1D vector containing the percentages
% we do not want to loop on every block of the domain because:
% - depending on the grid size there can be many
% - a large number of them can be empty
% So we first find the [X,Y] blocks which are not empty, we'll only loop on
% these blocks.
validbins = find(Nz) ; % find the indices of non-empty blocks
[xbins,ybins] = ind2sub([nbins,nbins],validbins) ; % convert linear indices to 2d indices
nv = numel(xbins) ; % number of block to process
% Now for each [X,Y] block, we get the distribution over a [Z] column and
% assign the results to the full 3D matrices
for k=1:nv
% this block coordinates
xbin = xbins(k) ;
ybin = ybins(k) ;
% find linear indices of the `x` and `y` values which are located into this block
idx = find( binX==xbin & binY==ybin ) ;
% make a subset with the corresponding 'z' value
subZ = z(idx) ;
% find the distribution and assign to 3D matrices
[Nz,~,zbins] = histcounts( subZ , binEdges ) ;
N3d(xbin,ybin,:) = Nz ; % total counts for this block
Npc(xbin,ybin,:) = Nz ./ npt ; % density % for this block
% Now we have to assign this value (color or percentage) to all the points
% which were found in the blocks
vzbins = find(Nz) ;
for kz=1:numel(vzbins)
thisColorpc = Nz(vzbins(kz)) ./ npt * 100 ;
idz = find( zbins==vzbins(kz) ) ;
idx3d = idx(idz) ;
colorpc(idx3d) = thisColorpc ;
end
end
assert( sum(sum(sum(N3d))) == npt ) % double check we counted everything
%% Display final result
h=figure;
hs=scatter3(x, y, z, 3 , colorpc ,'filled' );
xlabel('X'),ylabel('Y'),zlabel('Z')
cb = colorbar ;
cb.Label.String = 'Probability density estimate';
As I said at the beginning, the result is slightly different than your example image. This sample set yields the following distribution:
If you want a way to "double check" that the results are not garbage, you can look at the 2D density results on each axis, and check that it matches the apparent distribution of your points:
%% Verify on 3 axis:
Nz = histcounts2(y,x,binEdges,binEdges) ./ npt *100 ;
Nx = histcounts2(z,y,binEdges,binEdges) ./ npt *100 ;
Ny = histcounts2(x,z,binEdges,binEdges) ./ npt *100 ;
figure
ax1=subplot(1,3,1) ; bz = plotDensity(Nz,ax1) ; xlabel('X'),ylabel('Y') ;
ax2=subplot(1,3,2) ; bx = plotDensity(Nx,ax2) ; xlabel('Y'),ylabel('Z') ;
ax3=subplot(1,3,3) ; by = plotDensity(Ny,ax3) ; xlabel('Z'),ylabel('X') ;
Click on the image to see it larger:
The code for plotDensity.m:
function hp = plotDensity(Ndist,hax)
if nargin<2 ; hax = axes ; end
hp = bar3(Ndist,'Parent',hax) ;
for k = 1:length(hp)
zdata = hp(k).ZData;
hp(k).CData = zdata;
hp(k).FaceColor = 'interp';
end
shading interp
I have a routine that iteratively changes the structure of a matrix and I would like to animate the process, so that a user can actually see the structure changing.
If my matrix is nxn, I want to display the matrix as an nxn table
e.g.
Then using the 'plot' command I would like to have a figure containing:
(gridlines not necessary)
My actual matrix could be 25x25 / 100x100
This short piece of code
n = 25 ;
A = randi(100, n, n) ;
figure ;
xlim([0 n+2]) ;
ylim([0 n+2]) ;
axis off ;
tt = cell(n,n) ;
for i = 1:n
for j = 1:n
tt{i,j} = text(j,n-i,sprintf('%d',A(i,j))) ;
end
end
for k = 1:100
% Get random coordinates
i = randi(n) ;
j = randi(n) ;
% Get random value
v = randi(100) ;
% Remove & update text
delete(tt{i,j}) ;
tt{i,j} = text(j,n-i,sprintf('%d',v)) ;
% Force figure plot
drawnow() ;
% Wait a bit
pause(0.02) ;
end
prints the matrix on a grid, without the lines. It then randomly finds coordinates and change their value.
I'm not sure adding lines help clarity. If really needed, you can simply add them using plot.
You can adjust the sprintf so that it displays your data nicely.
If number are all on top of each other, you can increase the size of the figure by simply dragging the corner of the window.
Let A be an n by 3 matrix, such that the first two columns are all ordered pairs of the form (5*i,5*i) for i from 1 to 200. The third column contains values from 0 to 1, which I will call intensities. I want to make a 1000 by 1000 plot so that the rectangle at (5*i,5*i) is shaded with intensity described by the third column entry.
I'm familiar with the heatmap function and imshow, but I don't see a way to include this "scaling by 5" to make a nice plot. And of course in general the x and y coordinates may not be scaled by the same amount.
Is there a nice way to do this in Matlab?
With imagesc it's actually pretty simple:
First some example data:
%// generate example data
ii = 1:200;
[xx,yy] = meshgrid(ii);
A(:,1) = 5*xx(:);
A(:,2) = 5*yy(:);
A(:,3) = randi([0,1],1,40000);
Actual answer
n = 200;
%// reshape data
D = reshape( A(:,3),n,n );
%// heatmap
imagesc(A(:,1),A(:,2),D)
colormap(gray)
caxis([0,1])
gives:
Important notice
If your coordinates are not sorted as required for imagesc you can sort them with:
A = sortrows(A,[2,1]);
Clown Example
%// original image
load clown
I = reshape(1:numel(X),size(X));
[R,C] = ind2sub(size(X),I);
A(:,1) = R(:);
A(:,2) = C(:);
A(:,3) = X(:);
D = reshape( A(:,3),200,320 );
figure(1)
subplot(1,3,1)
imagesc(A(:,1),A(:,2),D)
%// shuffled image -> shuffled data
shuffle = randperm(320*200);
A = A(shuffle,:);
D = reshape( A(:,3),200,320 );
subplot(1,3,2)
imagesc(A(:,1),A(:,2),D)
%// sorted image
A = sortrows(A,[2,1]);
D = reshape( A(:,3),200,320 );
subplot(1,3,3)
imagesc(A(:,1),A(:,2),D)
You see, even if your coordinates are sorted like a mess, you can rebuild the image with sortrows.
See this
function DrawHeatmap(X,Y,Z)
%DRAWHEATMAP Draw a 2D heatmap for (X,Y) coordinates whose values are in Z
% X, Y , Z must be columns
% By: Eng. Osama Talaat Abdel-Hafiz - PhD Student
% Egypt - Sept 2017
if size(X,2)==1 && size(Y,2)==1 && size(Z,2)==1
F = scatteredInterpolant(X,Y,Z); % create a function from interpolation
[X,Y] = meshgrid(min(X):0.1:max(X),min(Y):0.1:max(Y));
Z = F(X,Y);
contourf(X, Y, Z, linspace(floor(min(min(Z))),ceil(max(max(Z))),400), 'LineColor','none')
colorbar;
else
error('X, Y , Z must be columns')
end
end
I'm looking to use Matlab to run through a set of data, 5446100 x 6 called xdata1.
I'm looking it to plot the first 100 data points, and after this to run through each point individually.
To start I have:
for i=1:100
NOC1 = CPMD_PCA(xdata1(99+i,:);
How would I then get this to carry on continuously, other than writing out i=2:101, i=3:102 etc
I am not sure about what CPMD_PCA is doing so I'll show you how to plot a moving windows on a simple matlab plot, on a sample data set.
You'll have to adjust the code to your exact application.
%% // just to create a sample set
x = ( 0:pi/10:150*pi ).' ; %//'
y = cos(x).*cos(3*x)+cos(x./4) ;
xdata = [y , -y/2] ;
%% // adjust moving window size and compute other useful parameters
window_size = 100 ; %// the length (in points) of the moving window
nPts = size(y,1) ; %// number of points in a column
n_windows = nPts-window_size ; %// the total number of windows
%% // Do the initial plot (not moving yet) of the first 100 points
idx = 1:window_size ;
hp = plot( xdata( idx , 1) ) ;
%% // Move the plot by one point at each iteration of the loop
for iwin =1:n_windows
idx = idx + 1 ;
set( hp , 'YData' , xdata(idx,1) )
drawnow %// you can comment that line (try with and without)
pause(0.1) %// adjust the timing to your needs
end
To answer you question directly you can wrap your current code in another loop:
for outer_loop_index = 1 : (size(xdata1,1) - 100)
for inner_loop_index = outer_loop_index : outer_loop_index + 99
NOC1 = CPMD_PCA(xdata1(inner_loop_index,:);
end
end