Adding colors to lines of a stairstep plot - matlab

I am trying to plot a hypnogram (graph that shows sleep cycles) and am currently using stairstep function to plot it. Below is a sample data since the one I am working with is huge:
X = linspace(0,4*pi,10);
Y = sin(X);
stairs(X,Y)
How do I make the lines of every ticks/score on the y-axis have a unique color? Which looks something like this:

One way to do it would be to segregate your data into as many dataset as your have flat levels, then plot all these data sets with the required properties.
There is however a way to keep the original dataset into one piece. If we consider your initial example data:
X = linspace(0,4*pi,10);
Y = sin(X);
step 1: recreate a "stair" like data set
Then by recombining the elements of X and Y we can obtain the exact same output than with the stairs function:
x = reshape( [X;X], 1,[] ); x(1) = [] ; % duplicate each element, remove the first one
y = reshape( [Y;Y], 1,[] ); y(end) = [] ; % duplicate each element, remove the lastone
hp = plot(x,y) ;
step 2: Use patch to be able to specify level colors
The patch object has many option for colouring faces, vertex and edges. The default patch object will try to close any profile given in coordinate by joining the first and last point. To override this behaviour, you just need to add a NaN element to the end of the coordinate set and patch will produce a simple line (but all the colouring options remain !).
To determine how many levels and how many colors we will need, we use the function unique. This will tell us how many unique levels exist in the data, and also we can associate each level with an index which will point to the color map.
%% Basic level colored line patch
% create profile for patch object
x = reshape([X;X],1,[]); x(1) = [] ; % same as above to get a "stairs" shape
y = reshape([Y;Y],1,[]); y(end) = [] ; % idem
xp = [x,NaN] ; % add NaN in last position so the patch does not close the profile
yp = [y,NaN]; % idem
% prepare colour informations
[uy,~,colidx] = unique(Y) ;
ncolor = length(uy) ; % Number of unique level
colormap(hsv(ncolor)) % assign a colormap with this number of color
% create the color matrix wich will be sent to the patch object
% same method of interleaving than for the X and Y coordinates
cd = reshape([colidx.';colidx.'],1,[]);
hp = patch(xp,yp,cd,'EdgeColor','interp','LineWidth',2) ;
colorbar
Yes! ... now our flat levels have a colour corresponding to them ... but wait, those pesky vertical lines are still there and polluting the graph. Could we colour them in a different way? Unfortunately no. No worries however, there is still a way to make them completely disappear ...
step 3: Use NaN to disable some segments
Those NaN will come to the rescue again. Any segment defined with a NaN will not be plotted by graphic functions (be it plot, patch, surf or any other ...). So what we can do is again interleave some NaN in the original coordinate set so only the horizontal lines will be rendered. Once the patch is created, we can build a second, "opposite", coordinate set where only the vertical lines are visible. For this second set, since we do not need fancy colouring, we can simply render them with plot (but you could also build a specific patch for that too if you wanted to colour them differently).
%% invisible vertical line patch + dashed vertical lines
% prepare profile points, interleaving NaN between each pair
vnan = NaN(size(X)) ;
xp = reshape([X;vnan;X],1,[]); xp([1:2 end]) = [] ;
yp = reshape([Y;Y;vnan],1,[]); yp(end-2:end) = [] ;
% prepare the vertical lines, same method but we interleave the NaN at one
% element offset
xv = reshape([X;X;vnan],1,[]); xv([1:3 end]) = [] ;
yv = reshape([Y;vnan;Y],1,[]); yv([1:2 end-1:end]) = [] ;
% prepare colormap and color matrix (same method than above)
[uy,~,colidx] = unique(Y) ;
ncolor = length(uy) ; % Number of unique level
colormap(hsv(ncolor)) % assign a colormap with this number of color
% create the color matrix wich will be sent to the patch object
% same method of interleaving than for the X and Y coordinates
cd = reshape([colidx.';colidx.';vnan],1,[]); cd(end-2:end) = [] ;
% draw the patch (without vertical lines)
hp = patch(xp,yp,cd,'EdgeColor','flat','LineWidth',2) ;
% add the vertical dotted lines
hold on
hv = plot(xv,yv,':k') ;
% add a label centered colorbar
colorbar('Ticks',((1:ncolor)+.5)*ncolor/(ncolor+1),'TickLabels',sprintf('level %02d\n',1:ncolor))
I have used the hsv colormap in the last example because your example seems to indicate that you do not need gradually progressing colors. You could also define a custom colormap with the exact color you want for each level (but that would be another topic, already covered many time if you search for it on Stack Overflow).
Happy R.E.M. sleeping !

Below code is not that efficient, but works well.
Basically, it draws line by line from left to right.
Firstly, generate sample data
num_stage = 6;
% generate sample point
x = linspace(0,1,1000)';
% generate its stage
y = round((sin(pi*x)+1)*(num_stage-1)/2)/(num_stage-1);
stage = unique(y); % find value of each stage
color_sample = rand(num_stage,3); % set color sample
Then we can draw like this
idx = find([1;diff(y)]); % find stage change
idx(end+1) = length(x)+1; % add last point
% display routine
figure;
% left end stage
k = 1;
% find current stage level
c = find(stage == y(idx(k)));
% plot bold line
plot(x([idx(k),idx(k+1)-1]),y(idx(k))*ones(2,1),'color',color_sample(c,:),'linewidth',5);
hold on;
for k = 2 : length(idx)-1
% find current stage level
c = find(stage == y(idx(k)));
% plot dashed line from left stage to current stage
plot(x([idx(k)-1,idx(k)]),[y(idx(k-1));y(idx(k))],'--','color',[0.7,0.7,0.7]);
% plot bold line for current stage with specified color
plot(x([idx(k),idx(k+1)-1]),y(idx(k))*ones(2,1),'color',color_sample(c,:),'linewidth',5);
end
% set x-axis
set(gca,'xlim',[x(1),x(end)]);
Following is result

Use if statement and divide it blocks. Check the criteria of Y-axis to be in a certain range and if it falls in that range, plot it there using the colors you want. For example if (y>1) plot(x,y,'r') else if (y some range) plot(x,y,'b'). Hope it helps

Related

Excluding values from surf plot

Suppose we have a surf plot like this:
A = round(peaks,0);
surf(A)
Is there any way to modify the colormap so that it excludes all values in A that are equal to 0, as if they were NaN?
Colouring 0's as white without affecting the rest of the colourmap would be another acceptable solution.
You mentioned the solution: set all 0s to nan:
A = round(peaks, 0);
A(A==0)=nan; % in-place logical mask on zero entries
surf(A)
% The same, but with a temporary variable, thus not modifying A
B = A;
B(B==0) = nan;
surf(B)
Results in (R2007a):
If you do not want to modify A or use a temporary variable, you'll need to "cut a hole" in your colour map
A = round(peaks);
unique_vals = unique(A); % Get the unique values
cmap = jet(numel(unique_vals)); % Set-up your colour map
zero_idx = find(unique_vals==0); % find 0 index
cmap(zero_idx,:) = 1; % all ones = white. Nan would make it black
surf(A, 'EdgeColor', 'none')
colormap(cmap) % Use our colour map with hole
NB: the 'EdgeColor', 'none' pair is used to remove the edges of each patch, such that you don't see a "grid" where the values are 0.

Change color of specific points in matlab?

I have a scatterplot in Matlab and was wondering if there was any way to the change the color of just one of the points?
you can plot the graph, after that replot the point which you want.
% plot the curve or graph
hold on
plot(x,y,'.r')
Try to hold on and then plot the point (x,y) which you want with the specified color (r) which you are interested in.
If you do not want to overlay a second plot on the first, you can plot each point individually and use handles. In that way, you can later perform arbitrary changes on each individual point.
You can find an example below.
% Generate some numbers
x = randn(10,1);
y = randn(10,1);
% Plot each point individually
figure
hold on
for idx = 1 : numel(x)
hdl(idx) = plot(x(idx),y(idx),'marker','.','color','k')
end
% change color, markerstyle, x-position, etc...
hdl(2).Color = [1 0 0]
hdl(3).Marker = 'o'
hdl(5).XData = 1
x = rand(10,1) ;
y = rand(10,1) ;
scatter(x,y) ;
[x1,y1] = getpts ;
hold on
plot(x1,y1,'Or') ;
click on the point, you want to change color, when prompted.

Separating axes from plot area in MATLAB

I find that data points that lie on or near the axes are difficult to see. The obvious fix, of course, is to simply change the plot area using axis([xmin xmax ymin ymax]), but this is not preferable in all cases; for example, if the x axis is time, then moving the minimum x value to -1 to show activity at 0 does not make sense.
Instead, I was hoping to simply move the x and y axes away from the plot area, like I have done here:
left: MATLAB generated, right: desired (image editing software)
Is there a way to automatically do this in MATLAB? I thought there might be a way to do it by using the outerposition axes property (i.e., set it to [0 0 0.9 0.9] and drawing new axes where they originally were?), but I didn't get anywhere with that strategy.
The answers here already show you most of the way - here is the last step to separate the x and y axle as per the example you put together.
f = figure ( 'color', 'white' );
% create the axes and set some properties
ax = axes ( 'parent', f, 'box', 'off', 'nextplot', 'add', 'XMinorTick', 'on', 'YMinorTick', 'on' );
% plot some data
plot ( ax, 0:10, [0:10].^2, 'rx-' )
% modify the x and y limits to below the data (by a small amount)
ax.XLim(1) = ax.XLim(1)-(ax.XTick(2)-ax.XTick(1))/4;
ax.YLim(1) = ax.YLim(1)-(ax.YTick(2)-ax.YTick(1))/4;
% Set the tick direction
ax.TickDir = 'out';
% draw the plot to generate the undocumented vertex data var
drawnow()
%% R2015a
% X, Y and Z row of the start and end of the individual axle.
ax.XRuler.Axle.VertexData(1,1) = 0;
ax.YRuler.Axle.VertexData(2,1) = 0;
%% R2015b
% extract the x axis vertext data
% X, Y and Z row of the start and end of the individual axle.
vd = get(ax.XAxis.Axle,'VertexData');
% reset the zero value
vd(1,1) = 0;
% Update the vertex data
set(ax.XAxis.Axle,'VertexData',vd);
% repeat for Y (set 2nd row)
vd = get(ax.YAxis.Axle,'VertexData');
vd(2,1) = 0;
set(ax.YAxis.Axle,'VertexData',vd);
Edit: The vertex is something that Matlab recreates whenever the axes/figure changes size or if you zoom or pan for example.
You can try to counteract this (remember you are using undocumented features here) by adding a listener to attempt to capture this. We can use the MarkedClean event which is called quite a lot of times.
addlistener ( ax, 'MarkedClean', #(obj,event)resetVertex(ax) );
Where you resetVertex function is something like: (R2015b shown only)
Edit 2 added the code to turn off the minor ticks below 0.
function resetVertex ( ax )
% extract the x axis vertext data
% X, Y and Z row of the start and end of the individual axle.
ax.XAxis.Axle.VertexData(1,1) = 0;
% repeat for Y (set 2nd row)
ax.YAxis.Axle.VertexData(2,1) = 0;
% You can modify the minor Tick values by modifying the vertex data
% for them, e.g. remove any minor ticks below 0
ax.XAxis.MinorTickChild.VertexData(:,ax.XAxis.MinorTickChild.VertexData(1,:)<0) = [];
ax.YAxis.MinorTickChild.VertexData(:,ax.YAxis.MinorTickChild.VertexData(2,:)<0) = [];
end
Note: this uses undocumented features -> so may only work in certain versions of Matlab (I have added the code for r2015a & r2015b) and Matlab may recreate the vertex data depending on what you do with the plots..
Here is a simple way for achieving that:
% some data:
x = 1:100;
f=#(x) 5.*x;
y=f(x)+rand(1,length(x))*50;
close all
% plotting:
f1 = figure('Color','white');
ax = axes;
plot(ax,x,y,'o');
% 'clean' the data area a little bit:
box off
ax.TickDir = 'out';
% pushing the axis a bit forward:
lims = axis;
pos = ax.Position;
axis([lims(1)-ax.XTick(2)/5 lims(2)+0.1 lims(3)-ax.YTick(2)/5 lims(4)+0.1])
% Create lines
firstXtick = 0.013; %this value need to be adjusted only once per figure
firstYtick = 0.023; %this value need to be adjusted only once per figure
lx = annotation(f1,'line',[pos(1) pos(1)+firstXtick],...
[pos(2) pos(2)],'Color',[1 1 1],'LineWidth',1);
ly = annotation(f1,'line',[pos(1) pos(1)],...
[pos(2) pos(2)+firstYtick],'Color',[1 1 1],'LineWidth',1);
Which yields this figure:
The only thing to adjust here, once per type of figure, is firstXtick and firstYtick values, that have to be fine tuned to the specific axis. After setting them to the correct value the figure can be resized with no problem. Zoom and pan require a little fixes.
You can start your axes from less than zero and then remove the less than zero ticks from your plot. e.g.
plot(0:3:30,0:3:30); %Some random data for plotting
h = gca;
axis([-1 30 -1 30]); %Setting the axis from less than zero
box off; %Removing box
h.TickDir = 'out'; %Setting Direction of ticks to outwards
h.XTickLabel(1)= {' '}; %Removing the first tick of X-axis
h.YTickLabel(1)= {' '}; %Removing the first tick of Y-axis
With this code, you'll get this result:
This may have a drawback, sometimes, that zero ticks may also get removed (as you can see in above figure). This is because the plot had set the first ticks of axes equal to zero. This can be avoided using if condition. So, the code can be modified as below:
plot(0:3:30,0:3:30);
h = gca;
axis([-1 30 -1 30]);
box off;
h.TickDir = 'out';
if str2num(cell2mat(h.XTickLabel(1))) <0
h.XTickLabel(1)= {' '};
end
if str2num(cell2mat(h.YTickLabel(1))) <0
h.YTickLabel(1)= {' '};
end
The above code will yield the following result:-
Also note that, for your case, since your axes ticks are very less, -1 may not be much suitable for the starting value of axes and you may need to use -0.1 instead i.e. axis([-0.1 30 -0.1 30]);
With a slight modification of #matlabgui's answer you can track the (major) tick limits:
ax = gca();
% Set the tick direction
ax.TickDir = 'out';
% Make sure this stays when saving, zooming, etc
addlistener ( ax, 'MarkedClean', #(obj,event) change_ticks(ax) );
% Draw the plot to generate the undocumented vertex data variable
% and call callback for the first time
drawnow();
The callback
function change_ticks( ax )
% Modify ruler
ax.XRuler.Axle.VertexData(1,1) = ax.XTick(1);
ax.XRuler.Axle.VertexData(1,2) = ax.XTick(end);
ax.YRuler.Axle.VertexData(2,1) = ax.YTick(1);
ax.YRuler.Axle.VertexData(2,2) = ax.YTick(end);
end
I haven't test extensively but seems to work for custom ticks too. This nicely cuts the rulers not only on zero but beyond the fist and last tick. This was tested in Matlab 2019a on Windows and ax.XRuler.Axle.VertexData works just fine. Note this is only for major ticks!

multiple matlab contour plots with one level

I have a number of 2d probability mass functions from 2 categories. I am trying to plot the contours to visualise them (for example at their half height, but doesn't really matter).
I don't want to use contourf to plot directly because I want to control the fill colour and opacity. So I am using contourc to generate xy coordinates, and am then using fill with these xy coordinates.
The problem is that the xy coordinates from the contourc function have strange numbers in them which cause the following strange vertices to be plotted.
At first I thought it was the odd contourmatrix format, but I don't think it is this as I am only asking for one value from contourc. For example...
contourmatrix = contourc(x, y, Z, [val, val]);
h = fill(contourmatrix(1,:), contourmatrix(2,:), 'r');
Does anyone know why the contourmatrix has these odd values in them when I am only asking for one contour?
UPDATE:
My problem seems might be a failure mode of contourc when the input 2D matrix is not 'smooth'. My source data is a large set of (x,y) points. Then I create a 2D matrix with some hist2d function. But when this is noisy the problem is exaggerated...
But when I use a 2d kernel density function to result in a much smoother 2D function, the problem is lessened...
The full process is
a) I have a set of (x,y) points which form samples from a distribution
b) I convert this into a 2D pmf
c) create a contourmatrix using contourc
d) plot using fill
Your graphic glitches are because of the way you use the data from the ContourMatrix. Even if you specify only one isolevel, this can result in several distinct filled area. So the ContourMatrix may contain data for several shapes.
simple example:
isolevel = 2 ;
[X,Y,Z] = peaks ;
[C,h] = contourf(X,Y,Z,[isolevel,isolevel]);
Produces:
Note that even if you specified only one isolevel to be drawn, this will result in 2 patches (2 shapes). Each has its own definition but they are both embedded in the ContourMatrix, so you have to parse it if you want to extract each shape coordinates individually.
To prove the point, if I simply throw the full contour matrix to the patch function (the fill function will create patch objects anyway so I prefer to use the low level function when practical). I get the same glitch lines as you do:
xc = X(1,:) ;
yc = Y(:,1) ;
c = contourc(xc,yc,Z,[isolevel,isolevel]);
hold on
hp = patch(c(1,1:end),c(2,1:end),'r','LineWidth',2) ;
produces the same kind of glitches that you have:
Now if you properly extract each shape coordinates without including the definition column, you get the proper shapes. The example below is one way to extract and draw each shape for inspiration but they are many ways to do it differently. You can certainly compact the code a lot but here I detailed the operations for clarity.
The key is to read and understand how the ContourMatrix is build.
parsed = false ;
iShape = 1 ;
while ~parsed
%// get coordinates for each isolevel profile
level = c(1,1) ; %// current isolevel
nPoints = c(2,1) ; %// number of coordinate points for this shape
idx = 2:nPoints+1 ; %// prepare the column indices of this shape coordinates
xp = c(1,idx) ; %// retrieve shape x-values
yp = c(2,idx) ; %// retrieve shape y-values
hp(iShape) = patch(xp,yp,'y','FaceAlpha',0.5) ; %// generate path object and save handle for future shape control.
if size(c,2) > (nPoints+1)
%// There is another shape to draw
c(:,1:nPoints+1) = [] ; %// remove processed points from the contour matrix
iShape = iShape+1 ; %// increment shape counter
else
%// we are done => exit while loop
parsed = true ;
end
end
grid on
This will produce:

Create a tailored colorbar in matlab

I have to create a map to show how far o how close some values are from a range and give them colors in consequence. Meanwhile, values that are within that range should have another different color.
For example: only the results that are within [-2 2] can be considered valid. For the other values, colors must show how far are from these limits (-3 lighter than -5, darker)
I've tried with colorbar but I'm not able to set up a self-defined color scale.
Any idea??
Thanks in advance!
You need to define a colormap for the range of values you have.
The colormap is N*3 matrix, defining the RGB values of each color.
See the example below, for a range -10:10 and valid values v1,v2:
v1=-3;
v2=+3;
a = -10:10;
graylevels=[...
linspace(0,1,abs(-10-v1)+1) , ...
ones(1, v2-v1-1) , ...
linspace(1,0,abs(10-v2)+1)];
c=repmat(graylevels , [3 1])';
figure;
imagesc(a);
colormap(c);
Here is some code that I just put together to demonstrate a simple means of creating your own lookup table and assigning values from it to the image that you're working with. I'm assuming that your results are in a 2D array and I just used randomly assigned values, but the concept is the same.
I mention the potentila use of HSV as a coloring scheme. Just note that, that requires you to have a m by n by 3 matrix. The top layer is the H - hue, 2nd being the S - saturation and the 3rd being the V or value (light/dark). Simply set the H and S to whatever values you want for the color and vary the V in a similar manner as shown below and you can get the varied light and dark color you want.
% This is just assuming a -10:10 range and randomly generating the values.
randmat = randi(20, 100);
randmat = randmat - 10;
% This should just be a black and white image. Black is negative and white is positive.
figure, imshow(randmat)
% Create your lookup table. This will illustrate having a non-uniform
% acceptable range, for funsies.
vMin = -3;
vMax = 2;
% I'm adding 10 here to account for the negative values since matlab
% doesn't like the negative indicies...
map = zeros(1,20); % initialized
map(vMin+10:vMax+10) = 1; % This will give the light color you want.
%linspace just simply returns a linearly spaced vector between the values
%you give it. The third term is just telling it how many values to return.
map(1:vMin+10) = linspace(0,1,length(map(1:vMin+10)));
map(vMax+10:end) = linspace(1,0,length(map(vMax+10:end)));
% The idea here is to incriment through each position in your results and
% assign the appropriate colorvalue from the map for visualization. You
% can certainly save it to a new matrix if you want to preserve the
% results!
for X = 1:size(randmat,1)
for Y = 1:size(randmat,2)
randmat(X,Y) = map(randmat(X,Y)+10);
end
end
figure, imshow(randmat)