How to set arbitrary colors for bars in a 3D bar plot? - matlab

Say that I have a matrix Z with some values, and I want to illustrate it by a plotting the values in Z by height. The first solution comes to mind is a surface, but using surf and similar functions with small matrices doesn't look good.
So I thought about using something like a 3D bar plot with bar3. But the problem is that this function always sets the color by the group and not by height, and I can't get it to do so.
Here is an example:
Z = peaks(5);
subplot 121
surf(Z)
title('Surface look bad')
subplot 122
bar3(Z)
title('The color is not by height')
I tried to look for the color properties in the handles returned by bar3 (like CData and FaceColor) but got lost with all the values and how they relate to the bars themselves.
Ultimately, I would like to have a general solution that for 2 matrices Z and C I can create a 3D bar plot with bars in height given by Z and color given by C.
How can I do so?

The function bar3 returns a surface object, one for each group (i.e. one for each color), so all the bars in one group are essentially plotted as one 'broken' surface. This is explained very good in this answer, so I won't repeat it here.
Instead, I'll get to the solution for this specific problem. The relevant property of the surface is CData. When we create the bar plot, each surface's CData is assigned with a matrix in some size (we'll get to this) that is all equal one value. A different value for each surface. This is how the figure as a whole translates its color map to the color of the groups.
As written above (and elaborated in the linked answer), each group represented by a surface, so it takes a whole matrix to define the color at each point of the surface. The first thing we want to do is to get this matrix size:
Z = peaks(5);
bar_h = bar3(Z);
% we take only the first one, but they are all the same size:
cdata_sz = size(bar_h(1).CData)
cdata_sz =
30 4
CData has always 4 columns (see here why), and the number of rows is always 6*number of groups. This is because it takes 5 vertices to create one closed rectangle with an area object (the last vertex is like the first one) and one line is for spacing between the bars with NaNs, so they will look separated.
Next, we need to enlarge our original colormap (which is the same size of Z) to fit CData in the right way. Essentially, we just want to repeat the same value for all vertices that belong to the same bar. Assuming Z is also our color data (i.e. we color by height) we do:
z_color = repelem(Z,6,4)
Now we need to split our z_color to different cells in the number of our groups. Each cell will contain the coloring data for one surface object:
z_color = mat2cell(z_color,cdata_sz(1),ones(1,size(Z,2))*cdata_sz(2));
And finally, we apply the new color data to the bar plot:
set(bar_h,{'CData'},z_color.')
As a bonus, if we want to remove all zero values from our bar, it can be done easily by setting them to NaN:
Z(abs(Z)<eps) = nan;
C(isnan(Z)) = nan; % if we use a colormap C different from Z
All the above could be boiled down to this handy function:
function bar_h = Cbar3(Z,C,b,y)
% Z - The data
% C - CData (if other then Z values)
% b - Minimum absolute value to keep colored
% y - y-axis values to order the data by
if nargin<2, C = Z; end
if nargin<3 || isempty(b), b = 0; end
Z(abs(Z)<b) = nan;
C(isnan(Z)) = nan;
if nargin<4
bar_h = bar3(Z);
else
bar_h = bar3(y,Z);
end
cdata_sz = size(bar_h(1).CData);
z_color = repelem(C,6,4);
z_color = mat2cell(z_color,...
cdata_sz(1),ones(1,size(Z,2))*cdata_sz(2));
set(bar_h,{'CData'},z_color.')
end
Example of usage:
subplot 121
Z = peaks(30);
Cbar3(Z,Z,0.5);
pbaspect auto
shading flat % just to get a cleaner look
title('Cbar3 using height as color')
subplot 122
Cbar3(Z,rand(size(Z)),0.5);
pbaspect auto
shading flat % just to get a cleaner look
title('Cbar3 using random as color')
Result:

This is a partial answer.
The case of using the bar height as color is covered by the official MATLAB documentation. Essentially the example code boils down to:
function q45423394
hB = bar3(peaks(25)); colorbar;
for indE = 1:numel(hB)
hB(indE).CData = hB(indE).ZData;
end
All you need to do afterwards is make sure that the colormap is the one you want.

While I find EBH's solution aesthetically more pleasing, here there is a simpler solution: interpolation
z = peaks(5);
[x,y]=meshgrid(1:0.1:size(z,1),1:0.1:size(z,2));
zn=interp2(z,x,y,'nearest');
% plot it
surf(zn,'edgecolor','none','facecolor','interp')

Related

How to color multiple lines based on their value?

I produced a plot that contains 50 curves and each of them corresponds to a specific value of a parameter called "Jacobi constant", so I have 50 values of jacobi constant stored in array called jacobi_cst_L1:
3.000900891023230
3.000894276927840
3.000887643313580
3.000881028967010
3.000874419173230
3.000867791975870
3.000861196034850
3.000854592397690
3.000847948043080
3.000841330136040
3.000834723697250
3.000828099771820
3.000821489088600
3.000814922863360
3.000808265737810
3.000801695858850
3.000795067776960
3.000788475204760
3.000781845363950
3.000775192199620
3.000768609354090
3.000761928862980
3.000755335851910
3.000748750854930
3.000742084743060
3.000735532899990
3.000728906460450
3.000722309400740
3.000715644446600
3.000709016645110
3.000702431180730
3.000695791284050
3.000689196186970
3.000682547292110
3.000675958537960
3.000669315388860
3.000662738391370
3.000656116141060
3.000649560630930
3.000642857256680
3.000636330415510
3.000629657944820
3.000623060310100
3.000616425935580
3.000609870077710
3.000603171772120
3.000596554947660
3.000590018845460
3.000583342259840
3.000576748353570
I want to use a colormap to color my curves and then show in a lateral bar the legend that show the numerical values corresponding to each color of orbit.
By considering my example image, I would want to add the array of constants in the lateral bar and then to color each curve according the lateral bar.
% Family of 50 planar Lyapunov orbits around L1 in dimensionless unit
fig = figure;
for k1 = 1:(numel(files_L1_L2_Ly_prop)-2)
plot([Ly_orb_filt(1).prop(k1).orbits.x],[Ly_orb_filt(1).prop(k1).orbits.y],...
"Color",my_green*1.1); hold on %"Color",my_green*1.1
colorbar()
end
axis equal
% Plot L1 point
plot(Ly_orb_filt_sys_data(1).x,Ly_orb_filt_sys_data(1).y,'.',...
'color',[0,0,0],'MarkerFaceColor',my_green,'MarkerSize',10);
text(Ly_orb_filt_sys_data(1).x-0.00015,Ly_orb_filt_sys_data(1).y-0.0008,'L_{1}');
%Primary bodies plots
plot(AstroData.mu_SEM_sys -1,0,'.',...
'color',my_blue,'MarkerFaceColor',my_blue,'MarkerSize',20);
text(AstroData.mu_SEM_sys-1,0-0.001,'$Earth + Moon$','Interpreter',"latex");
grid on;
xlabel('$x$','interpreter','latex','fontsize',12);
ylabel('$y$','interpreter','latex','FontSize',12);
How can I color each line based on its Jacobi constant value?
You can use any colour map to produce a series of RGB-triplets for the plotting routines to read (Or create an m-by-3 matrix with elements between 0 and 1 yourself):
n = 10; % Plot 10 lines
x = 1:15;
colour_map = jet(n); % Get colours. parula, hsv, hot etc.
figure;
hold on
for ii = 1:n
% Plot each line individually
plot(x, x+ii, 'Color', colour_map(ii, :))
end
colorbar % Show the colour bar.
Which on R2007b produces:
Note that indexing into a colour map will produce linearly spaced colours, thus you'll need to either interpolate or calculate a lot to get the specific ones you need. Then you can (need to?) modify the resulting colour bar's labels by hand to reflect your input values. I'd simply use parula(50), treat its indices as linspace(jacobi(1), jacobi(end), 50) and then my_colour = interp1(linspace(jacobi(1), jacobi(end), 50), parula(50), jacobi).
So in your code, rather than using "Color",my_green*1.1 for each line, use "Color",my_colour(kl,:), where my_colour is whatever series of RGB triplets you have defined.

How to make 2D scatter plot in Matlab with colors depending on values?

I have three vectors of the same lenght: x, y, and cls. I want to make a 2D plot of x and y but each point should have a color corresponding to the value of cls.
I thought about using the scatter function but you can chage the color of the whole plot, not of particular elements. Any ideas?
I would like to get something like in this example, when cls has elements of three values:
From the help of scatter:
scatter(x,y,a,c) specifies the circle colors. To plot all circles with the same color, specify c as a single color string or an RGB triplet. To use varying color, specify c as a vector or a three-column matrix of RGB triplets.
you can construct c as
c=zeros(size(x),3);
c(cls==1,:)=[1 0 0]; % 1 is red
% ...
scatter(x,y,1,c)
However, I dont know how to do the background. Did you apply some Machine learning algorithm to clasify the data? maybe you can get the equations to plot the background from there, but it depends on the method.
If you have the Statistics Toolbox, there is an easy way of doing this, it's called gscatter.
It takes similar inputs to scatter, but the third input is the group:
gscatter(x,y,cls)
You can add colours and markers - this plots with red, then green, then blue (order determined by the contents of cls, all markers circles.
gscatter(x,y,cls,'rgb','o')
Here's another solution splitting your data in three using logical indexing:
% Some random data
x = rand(100,1);
y = rand(100,1);
cls = round(2*rand(100,1));
% Split the data in three groups depending on the value in cls
x_red = x(cls==0);
y_red = y(cls==0);
x_green = x(cls==1);
y_green = y(cls==1);
x_blue = x(cls==2);
y_blue = y(cls==2);
% plot the data
scatter(x_red,y_red,1,'r')
hold on
scatter(x_green,y_green,1,'g')
scatter(x_blue,y_blue,1,'b')
hold off
One very simple solution with c being the color vector:
scatter3(X,Y,zeros(size(X,1)),4,c);
view(0,90);

How to mark points in a plot that are over a specified value in Matlab?

Say I have a data set with x values of longitude and Y values of 1 to 100. How can I plot the whole data set and represent all Y values over 90 with a different symbol?
Thanks for the help!
The easiest way would be to plot the sets separately, and specify a different symbol for each set i.e.
plot(x(Y<=90),Y(Y<=90),'bx',x(Y>90),Y(Y>90),'bo');
You could also do different colors. The scatter function has the ability to specify a different color for each point with the syntax scatter(x,y,s,c). For your example, you could do:
% make data
rng(0,'twister'); theta = linspace(0,2*pi,150);
x = sin(theta) + 0.75*rand(1,150); x = x*100;
y = cos(theta) + 0.75*rand(1,150); y = y*100;
mask = y>90;
% plot with custom colors for each point
c = zeros(numel(x),3); % matrix of RGB colorspecs
c(mask,:) = repmat([1 0 0],nnz(mask),1); % red
c(~mask,:) = repmat([0 0 1],nnz(~mask),1); % blue
scatter(x,y,10,c,'+');
Or instead of and RGB colorspec matrix, you can index into the current colormap. This allows you to get a nice smooth variation with some value:
scatter(x,y,10,y+x,'o') % x+y is mapped to indexes into default colormap, jet(64)
You can combine this color mapping with the approach of separating the data into two sets to also get different markers. Split the data, plot the first set with scatter as above, hold on, and plot the second set with a different marker. For example,
cv = x+y; % or just y, but this is an interesting example
scatter(x(mask),y(mask),10,cv(mask),'+');
hold on
scatter(x(~mask),y(~mask),10,cv(~mask),'o');
The result is different marker styles, where '+' is used where y>90 and '+' elsewhere, and different colors, where color is determined by mapping the values of cv=x+y onto the current colormap. The idea here is to look at 2 different modes of variations, but you could just use cv=y.

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)

MATLAB, Filling in the area between two sets of data, lines in one figure

I have a question about using the area function; or perhaps another function is in order...
I created this plot from a large text file:
The green and the blue represent two different files. What I want to do is fill in the area between the red line and each run, respectively. I can create an area plot with a similar idea, but when I plot them on the same figure, they do not overlap correctly. Essentially, 4 plots would be on one figure.
I hope this makes sense.
Building off of #gnovice's answer, you can actually create filled plots with shading only in the area between the two curves. Just use fill in conjunction with fliplr.
Example:
x=0:0.01:2*pi; %#initialize x array
y1=sin(x); %#create first curve
y2=sin(x)+.5; %#create second curve
X=[x,fliplr(x)]; %#create continuous x value array for plotting
Y=[y1,fliplr(y2)]; %#create y values for out and then back
fill(X,Y,'b'); %#plot filled area
By flipping the x array and concatenating it with the original, you're going out, down, back, and then up to close both arrays in a complete, many-many-many-sided polygon.
Personally, I find it both elegant and convenient to wrap the fill function.
To fill between two equally sized row vectors Y1 and Y2 that share the support X (and color C):
fill_between_lines = #(X,Y1,Y2,C) fill( [X fliplr(X)], [Y1 fliplr(Y2)], C );
You can accomplish this using the function FILL to create filled polygons under the sections of your plots. You will want to plot the lines and polygons in the order you want them to be stacked on the screen, starting with the bottom-most one. Here's an example with some sample data:
x = 1:100; %# X range
y1 = rand(1,100)+1.5; %# One set of data ranging from 1.5 to 2.5
y2 = rand(1,100)+0.5; %# Another set of data ranging from 0.5 to 1.5
baseLine = 0.2; %# Baseline value for filling under the curves
index = 30:70; %# Indices of points to fill under
plot(x,y1,'b'); %# Plot the first line
hold on; %# Add to the plot
h1 = fill(x(index([1 1:end end])),... %# Plot the first filled polygon
[baseLine y1(index) baseLine],...
'b','EdgeColor','none');
plot(x,y2,'g'); %# Plot the second line
h2 = fill(x(index([1 1:end end])),... %# Plot the second filled polygon
[baseLine y2(index) baseLine],...
'g','EdgeColor','none');
plot(x(index),baseLine.*ones(size(index)),'r'); %# Plot the red line
And here's the resulting figure:
You can also change the stacking order of the objects in the figure after you've plotted them by modifying the order of handles in the 'Children' property of the axes object. For example, this code reverses the stacking order, hiding the green polygon behind the blue polygon:
kids = get(gca,'Children'); %# Get the child object handles
set(gca,'Children',flipud(kids)); %# Set them to the reverse order
Finally, if you don't know exactly what order you want to stack your polygons ahead of time (i.e. either one could be the smaller polygon, which you probably want on top), then you could adjust the 'FaceAlpha' property so that one or both polygons will appear partially transparent and show the other beneath it. For example, the following will make the green polygon partially transparent:
set(h2,'FaceAlpha',0.5);
You want to look at the patch() function, and sneak in points for the start and end of the horizontal line:
x = 0:.1:2*pi;
y = sin(x)+rand(size(x))/2;
x2 = [0 x 2*pi];
y2 = [.1 y .1];
patch(x2, y2, [.8 .8 .1]);
If you only want the filled in area for a part of the data, you'll need to truncate the x and y vectors to only include the points you need.