How can I align plots/graphics in subplots in MATLAB? - matlab

I have 3 objects (a photo and 2 plots) to put into subplots on one figure. It should look like this:
But as one can notice, the photo should not be square but rectangle. I tried to make it this way (found here Matlab: How to align the axes of subplots when one of them contains a colorbar?):
main=subplot(4,4,[5,6,7,9,10,11,13,14,15]) %photo
imagesc(im);
axis('image')
pion=subplot(4,4,[8,12,16]); %right plot (rotated)
view(90, 90)
plot(ypion,Ppion,'.k');
poz=subplot(4,4,1:3); %upper plot
plot(xpoz,Ppoz,'.k');
pos1=get(poz,'Position')
pos2=get(main,'Position')
pos3=get(pion,'Position')
pos1(3) = pos2(3); %width for the upper plot
set(poz,'Position',pos1)
pos3(4) = pos2(4); %height for the right plot
set(pion,'Position',pos3)
All I get is like this:
How can I force the upper plot to have the width as the photo itself (not as the photo subplot)? Setting the equal widths of the subplots doesn't work, as the photo doesn't fill the subplot area.

The command axis image adjust the image axis ratio. So, in principle, if you adjust the plot ratios of the two plots to the same ratio, it will do what you want.
There is one caveat; the image is inherently 3 times wider or higher than the plots, due to the fact that you've plotted it in 3x3 subplots, vs 1x3 for the top and 3x1 for the right plots. So, you'll have to divide either the x or y ratios of the plots by 3.
Some example code:
clc, clf
% generate some bogus data
ypion = rand(500,1);
Ppion = 450*rand(500,1);
xpoz = rand(500,1);
Ppoz = 450*rand(500,1);
% Load photo
photoSub = subplot(4,4,[5,6,7,9,10,11,13,14,15]);
load mandrill
photo = imagesc([X,X]);
colormap(map)
axis image
photoAxs = gca;
photoAxsRatio = get(photoAxs,'PlotBoxAspectRatio')
% right plot
subplot(4,4,[8,12,16]);
plot(Ppion,ypion,'k.');
rightAxs = gca;
axis tight
% upper plot
subplot(4,4,[1 2 3]);
plot(xpoz,Ppoz,'k.');
topAxs = gca;
axis tight
% adjust ratios
topAxsRatio = photoAxsRatio;
topAxsRatio(2) = photoAxsRatio(2)/3.8; % NOTE: not exactly 3...
set(topAxs,'PlotBoxAspectRatio', topAxsRatio)
rightAxsRatio = photoAxsRatio;
rightAxsRatio(1) = photoAxsRatio(1)/3.6; % NOTE: not exactly 3...
set(rightAxs,'PlotBoxAspectRatio', rightAxsRatio)
This gives the following result:
Just to test, changing photo = imagesc([X,X]); to photo = imagesc([X;X]); gives this:
Note that I did not divide the ratios by 3 exactly; it only came out OK if I used factors closer to 4. I do not know why that is; AFAIK, a factor of 3 should do the trick...
Oh well, at least you have something to work with now :)

Here's a solution that removes the guesswork in the accepted answer. This solution is adapted from the original one posted here.
% adjust ratios
photoAxsratio = photoAxs.PlotBoxAspectRatio(1)/photoAxs.PlotBoxAspectRatio(2);
topAxsratio = photoAxsratio * photoAxs.Position(4)/topAxs.Position(4);
topAxs.PlotBoxAspectRatio = [topAxsratio, 1, 1];
rightAxsratio = rightAxs.Position(3) / (photoAxs.Position(3) / photoAxsratio);
rightAxs.PlotBoxAspectRatio = [rightAxsratio, 1, 1];
Preview:
A bit of explanation
Some of the explanation has been posted in the original post, I'm not going to repeat them here.
The idea is to calculate the correct aspect ratio for the figures required to be resized.
We have the following equations:
Photo.width = Photo.height * Photo.ratio
TopAxis.width = TopAxis.height * TopAxis.ratio
RightAxis.width = RightAxis.height * RightAxis.ratio
Let
TopAxis.width = Photo.width
RightAxis.height = Photo.height
We have
TopAxis.height * TopAxis.ratio = Photo.height * Photo.ratio
TopAixs.ratio = Photo.ratio * Photo.height / TopAxis.height
RightAxis.width / RightAxis.ratio = Photo.width / Photo.ratio
RightAxis.ratio = RightAxis.width / (Photo.width / Photo.ratio)

Since the release of matlab R2019b you can now use: tiledlayout and nexttile.
It is now easy to do:
% Load a random scary image
I = im2gray(imread('https://unwinnable.com/wp-content/uploads/2011/10/The-Ring-well.jpg'));
% Some computation...
Sx = sum(I)/max(sum(I));
Sy = sum(I,2)/max(sum(I,2));
% We create an empty 3x3 tiled layout:
% 1 2 3
% 4 5 6
% 7 8 9
tiledlayout(3, 3);
% Starting from the tile 1, we plot a 1x2 tile:
% 1 2 x
% x x x
% x x x
nexttile(1,[1 2])
plot(Sx,'k.')
% Starting from the tile 4, we plot a 2x2 tile:
% x x x
% 4 5 x
% 7 8 x
nexttile(4,[2 2])
imagesc(I)
colormap(gray(256))
% Starting from the tile 6, we plot a 2x1 tile:
% x x x
% x x 6
% x x 9
nexttile(6,[2 1])
plot(Sy,1:numel(Sy),'k.')
Matlab adjust the size of the plots automatically.
And we obtain:
Under the hood the tiled layout looks like this:

For this particular case I suggest using low-level axes directly instead of high-level subplot.
Use the 'OuterPosition' properties of the three axes objects you create to place them in the right place with the appropriate size.

If you want the image to be aligned to the axes(distorted image):
change axis('image') to axis('tight') .
If you want the image aspect ratio to remain, and to align the axes to the image:
Well this is quick and dirty, but after applying axis('tight'), resize the figure to the appropriate scale...

Related

Stretch a polarplot() slice

I'm plotting some polar coordinates in Matlab. See example below:
I'm only plotting a slice but can't figure out how to stretch/ expand this slice, as there is more space in the figure window than used. Speaking in pictures I'd like to 'open it like a fan'.
Minimal working example:
th = linspace(-pi/2,pi/2,100);
polarplot(th,1.1+zeros(size(th)),'g');
ax = gca;
thetalim([-5 5])
rlim([1.05 1.12])
I've tried using ax.Position = [0 0 1 1]; similar to how it works in Cartesian plots but this does not seem to work here.
Any commands or tips I'm missing so far?
I can only think of a workaround solution: increase the theta values passed to polarplot to get larger angles in the graph, and then relabel the theta axis:
stretch = 3; % stretch factor
th = linspace(-pi/2,pi/2,100);
polarplot(th*stretch,1.1+zeros(size(th)),'g'); % apply strech to theta
ax = gca;
thetalim([-5 5]*stretch) % take stretch into account when setting theta axis limit
rlim([1.05 1.12])
ax = gca;
ax.ThetaTickLabel = strsplit(num2str((ax.ThetaTick/stretch))); % correct theta labels
In the last line I am keeping the default theta ticks and relabelling them. You may prefer to specify other values manually, such as
ax.ThetaTick = stretch*(-5:5); % specify theta ticks, including stretch
ax.ThetaTickLabel = strsplit(num2str((ax.ThetaTick/stretch))); % correct theta labels

Figure of data overlayed and corresponding plot of graph in MATLAB

I have 11 binary datasets all size 297x258 saved in data1 and I would like to produce an image with these data overlapped, each assigned a different color with the background removed (white). An example of the desired output is shown in this image:
I generate the figures of these datasets using:
figure, imshow(data1{1}),axis image, colormap(jet)
Additionally, any help finding the average increase in size (increase in y-axis) between data1{1} and data1{11} for every x value increment is also appreciated. Each set of data data1{1}, data1{2}, ...data1{11} is representative of times 0, 1, ...11 respectively and I would like to plot a graph of the average increase in y-axis against the x-axis (1:297). Your help is very much appreciated. Thanks.
My thoughts so far are:
for x=1:x_dim % where xdim is 297 (along the X-axis)
for y=1:ydim % where ydim is 258 (along the y-axis)
% execute code to determine increase in y-direction between
% binary datasets data1{1}, data1{2},...data1{11}.
% Then compute average for growth in the y-direction between each time
end
% Plot figure of average increase in y-axis against x-axis.
end
So here is the code for the figures you wanted:
all_data = cell2mat(flipud(data1.'));
all_data = diag(repelem(linspace(1,70,11),size(data1{1},1)))*logical(all_data);
% option 1:
figure('Position',[450 100 200 900],'Color',[1 1 1]);
C = colormap('jet');
C = [1 1 1;C];
colormap(C)
imagesc(all_data)
axis image
axis off
% option 2:
all_data(1:size(data1{1},1):size(all_data,1),:) = nan(11,size(all_data,2));
stack = nan(size(all_data));
toprow = zeros(1,size(all_data,2));
for k = 1:size(all_data,2)
tmp = nonzeros(flipud(all_data(:,k)));
stack(1:numel(tmp),k) = tmp;
toprow(k) = numel(tmp);
end
figure('Position',[650 100 200 900],'Color',[1 1 1]);
C = colormap('jet');
C = [1 1 1;C];
colormap(C)
image(stack(1:max(toprow.'),:))
axis xy
axis off
Which gives this:
option 1 no the left, and option 2 on the right.

How do you rescale the height of a histogram?

I am having trouble plotting a histogram of the x-values of my data points together with a line showing the relationship between x and y, mainly because the scale in the y direction of the histogram is not of the same magnitude as the scale in the line plot. For example:
% generate data
rng(1, 'twister')
x = randn(10000,1);
y = x.^2
% plot line, histogram, then histogram and line.
subplot(3,1,1)
scatter(x, y, 1, 'filled')
ax = gca;
maxlim = max(ax.XLim); % store maximum y-value to rescale histogram to this value
subplot(3,1,2)
h = histogram(x, 'FaceAlpha', 0.2)
subplot(3,1,3)
scatter(x, y, 1, 'filled')
hold on
h = histogram(x, 'FaceAlpha', 0.2)
Produces the following:
where the line chart is completely obscured by the histogram.
Now, one might naively try to rescale the histogram using:
h.Values = h.Values/max(h.Values) * maxlim;
which gives
You cannot set the read-only property 'Values' of Histogram.
Alternatively one can get the bin counts using histcounts, but as far as I can tell, the bar function does not allow one to set the face alpha or have other configurability as per the call to histogram.
As discussed in the comments there are several solutions that depend on the version of Matlab you are using. To restate the problem, the histogram function allows you to control many graphics properties like transparency, but only gives you a limited number of options to change the height of the bars. With histcounts you can get the bar heights and rescale them however you want, but you must plot the bars yourself.
First option: use histogram
As you cannot rescale the histogram heights, you must plot them on separate axis.
From release 2016a and onwards, you can use yyaxis left for the scatter plot and yyaxis right for the histogram, see Matlab documentation:
Prior to this one must manually create and set separate y-axis. Although I have not found a good simple example of this, this is perhaps the most relevant answer here: plot two histograms (using the same y-axis) and a line plot (using a different y-axis) on the same figure
Using histcounts and manually creating a bar chart
Using my example, we can get counts as follows:
[Values, Edges] = histcounts(x);
And rescaling:
Values = Values / max(Values) * maxlim;
and finding centres of bars:
bar_centres = 0.5*(Edges(1:end-1) + Edges(2:end));
Up to release 2014a, bar charts had a 'children' property for the patches that allows transparency to be controlled, e.g.:
% plot histogram
b1 = bar(bar_centres,Values);
% change transparency
set(get(b1,'Children'),'FaceAlpha',0.3)
After 2014a bar charts no longer have this property, and to get around it I plot the patches myself using the code from this mathworks q&a, replicated here:
function ptchs = createPatches(x,y,offset,c,FaceAlpha)
%createPatches.m
% This file will create a bar plot with the option for changing the
% FaceAlpha property. It is meant to be able to recreate the functionality
% of bar plots in versions prior to 2014b. It will create the rectangular
% patches with a base centered at the locations in x with a bar width of
% 2*offset and a height of y.
% Ensure x and y are numeric vectors
validateattributes(x,{'numeric'},{'vector'});
validateattributes(y,{'numeric'},{'vector'});
validateattributes(c,{'char'},{'scalar'});
%#TODO Allow use of vector c
% Check size(x) is same as size(y)
assert(all(size(x) == size(y)),'x and y must be same size');
% Default FaceAlpha = 1
if nargin < 5
FaceAlpha = 1;
end
if FaceAlpha > 1 || FaceAlpha <= 0
warning('FaceAlpha has been set to 1, valid range is (0,1]');
FaceAlpha = 1;
end
ptchs = cell(size(x)); % For storing the patch objects
for k = 1:length(x)
leftX = x(k) - offset; % Left Boundary of x
rightX = x(k) + offset; % Right Boundary of x
ptchs{k} = patch([leftX rightX rightX leftX],...
[0 0 y(k) y(k)],c,'FaceAlpha',FaceAlpha, ...
'EdgeColor', 'none');
end
end
I made one change: that is, imposed the no edge condition. Then, it is perfectly fine to use:
createPatches(bin_centres, Values, 1,'k', 0.2)
to create the bars.

Background Image for Plot

Is there an easy way to put a bitmap image in the background of a Matlab plot which does not fill the whole available space und keeps its aspect ratio when the figure is resized?
TIA
I'm not quite sure to understand what you mean by
plot which does not fill the whole available space
however the following solution should help you solve your problem (or at least get you started).
Basically read an image (here grayscale) and display it using the imagesc command along with the grayscale colormap, then issue the hold on command and plot the data. Notice that you need to reverse the direction of the x-axis in order to get the right direction for the plot.
Here is the code:
clear
clc
close all
A = imread('cameraman.tif');
x = 1:10;
y = x;
figure
%// Notice the fliplr(A) to reverse the direction of the x data
imagesc([min(x(:)) max(x(:))], [min(y(:)) max(y(:))],fliplr(A));
colormap gray
%// Here reverse the direction of the x axis, otherwise the plot is
%// reversed
set(gca,'XDir','reverse')
hold on
plot(x,y,'--r')
axis off
And the result:
If your background image is RGB, you can use the image function: (modified from answer here): You need to flip the x data from the image for each channel separately, because fliplr only accepts 2D arguments:
DataXImage = linspace(min(x), max(x), size(A, 2));
DataYImage = linspace(min(y), max(y), size(A, 1));
%// flip dimensions for each channel
B = cat(3,fliplr(A(:,:,1)),fliplr(A(:,:,2)),fliplr(A(:,:,3)));
image(DataXImage, DataYImage, B, 'CDataMapping', 'scaled');
which, using the peppers.png image, gives this:
Is this what you had in mind? If not please tell me!
img = imread('myimage.png');
% set the range of the axes
% The image will be stretched to this.
min_x = 0;
max_x = 8;
min_y = 0;
max_y = 6;
% make data to plot - just a line.
x = min_x:max_x;
y = (6/8)*x;
imagesc([min_x max_x], [min_y max_y], img);
hold on;
plot(x,y);

Marker size unit proportionnal to the axis values

I have an X Y Z dataset.
X and Y are the 2D coordinates and Z is the intensity.
I plot the data using the scatter function:
markerSize=100;
scatter(x,y,markerSize,z,'s','fill');
I use the options 's' and 'fill' to get filled squares.
My problem is the markerSize value corresponds to the area of the marker, and its unit is points (1/72 of one inch).
The marker size is constant, even if I resize the figure plot. So that the gap between the data points increases when I increase the figure size.
What I would like is a constant marker size which is a constant of the axis unit. For instance, the marker size should be 5x5 (5 in X axis and 5 in Y axis).
Thanks for your help.
You want to make the size of markers proportional to the figure size.
The size of markers is controlled by SizeData parameter of the scattergroup object. The size of figure is stored in Position parameter of the figure object. The difficult part is to interactively resize the marker when the figure size is changed. So you need to use ResizeFcn callback and call setmarkersize function that you define.
function [ ] = setmarkersize( src, evnt )
% # get position of the figure (pos = [x, y, width, height])
pos = get(src, 'Position');
% # get the scattergroup object
h = get(get(src,'children'),'children');
% # resize the marker
relativesize = 0.5;
set(h,'SizeData', pos(3)*relativesize);
end
================================================
% # attach the callback to figure
f = figure('ResizeFcn', #setmarkersize);
h = scatter(x,y,markerSize,z,'s','fill');
You will have to set the marker size manually according to the actual figure size on screen. Using axes property Position you can convert data units to relative figure units. In the next step this size can be converted to the absolute size in points on screen. With that information you can set the marker size accordingly. In the following code snippet I've set the x/y axis limits and width/height of the axis to identical values, because the square marker area can only be calculated reasonably if the marker width is equal to the marker height.
Set marker size relative to data units
% test data
x = [25*rand(1,10) 2.5];
y = [25*rand(1,10) 2.5];
z = [rand(1,10) 0.5];
% relative marker size in squared normalized figure units
marker_rel = 5;
%% Set relative marker size (approximately)
scatter(x,y,100,z,'s','fill');
% Set identical x/y limits and set axes height=widht, so that markers
% really represent squares in data units
xlim([0 25]);
ylim([0 25]);
set(gca, 'Units', 'Points');
axpos_pt = get(gca, 'Position');
axpos_pt = [axpos_pt(1) axpos_pt(2) min(axpos(3:4)) min(axpos(3:4))];
set(gca, 'Position', axpos_pt);
grid on;
grid minor;
% Set marker size relative to data units
markerSize = (marker_rel * axpos_pt(3) / diff(xlim))^2;
set(get(gca, 'children'), 'sizedata', markerSize);
As it turns out, the displayed marker size is slightly smaller than expected. Obviously, there's some bounding box of unknown (at least to me) size, see here.
An alternative approach is to plot rectangles "manually" as shown in the following code snippet (same test data is used). When the figure is resized, the rectangles are resized as well without any special callback function being needed.
Draw rectangles manually
%% Use rectangles (exact)
figure;
axes;
cmp = colormap;
z_range = max(z) - min(z);
line_style = 'none'; % set to '-' to make the edges visible
for k = 1:length(x)
x_pos = x(k) - marker_rel/2;
y_pos = y(k) - marker_rel/2;
w = marker_rel;
h = marker_rel;
color = cmp( round(((z(k) - min(z))/z_range)*(length(cmp) - 1)) + 1, : );
rectangle('Position', [x_pos y_pos w h], 'FaceColor', color,...
'LineStyle', line_style);
end
grid on;
grid minor;
The above code produces the desired marker size:
In general, these are no squares. They can (and will) only be squares if xlim = ylim and absolute height of axis = absolute width of axis. I have shown in my first code snippet how to achieve this.
I found an answer on the Matlab central forum which does not use the useful dsxy2figxy function. Here is the link to it (link)
The code is the following:
x = rand(1,50);
y = rand(1,50);
s = 5; %Marker width in units of X
h = scatter(x,y); % Create a scatter plot and return a handle to the 'hggroup' object
%Obtain the axes size (in axpos) in Points
currentunits = get(gca,'Units');
set(gca, 'Units', 'Points');
axpos = get(gca,'Position');
set(gca, 'Units', currentunits);
markerWidth = s/diff(xlim)*axpos(3); % Calculate Marker width in points
set(h, 'SizeData', markerWidth^2)
The answer by ysakamoto doesn't work in Matlab 2014, where Mathworks changed figure handles from double to object. The following modification makes it work again:
function [ ] = setmarkersize( src, ~ )
% get position of the figure (pos = [x, y, width, height])
pos = get(src, 'Position');
% marker size
relativesize = 0.01;
% axes is not necessarily the only child of figure (e.g. colorbar may be first)
for i = 1:numel(src.Children)
if strcmpi(src.Children(i).Type, 'axes')
% make marker size depend on figure width
src.Children(i).Children.SizeData = pos(3) * relativesize;
break
end
end
end
Also, when creating the figure it will not set the marker size to the correct value until resized. So call setmarkersize explicitly:
f = figure('ResizeFcn', #setmarkersize);
...
setmarkersize(f)
Consider abandoning 'markers' and drawing directly on the axis using rectangles or circles. The 'rectangle' command with 'curvature' set to [1 1] is essentially an open circle. Units are in plot units, so each rectangle can be scaled:
rectangle('position', [rectCentX rectCentY widthInPlotUnits, heightInPlotUnits],'curvature',[1 1])