How do you match the width of figures in a tiledlayout when using axis equal?

23 vues (au cours des 30 derniers jours)
There are 4 figures combined in a tiledlayout. The respective ratio of their dimensions is 1:1, using axis equal. However, their widths do not match. How can this be adapted? See the MWE:
clear variables; close all; clc;
tiledlayout(4,1)
amplitudes = [200e-3,50e-3,10e-3,200e-6];
wavelenghts = [50,500e-3,50e-3,500e-6];
for i = 1:numel(amplitudes)
nexttile
axis equal
x = 0:(wavelenghts(i)/100):(wavelenghts(i)/2);
y = amplitudes(i)*(1-cospi(2*1/wavelenghts(i)*x));
hold on
area(-x,y)
area(x,y)
xlim([-1 1]*wavelenghts(i)/2)
ylim([0 max(y)])
clear x y;
end

Réponse acceptée

Benjamin Kraus
Benjamin Kraus le 1 Juil 2025
Now that I've gone through the exercise of doing that using tiledlayout, I think I would suggest using neither tiledlayout nor subplot. In @Mathieu NOE's code, he is using subplot, but then manually specifying positions using pixel positions, but at that point there is no point in using subplot. @Mathieu NOE's code is also complicated by trying to do all the layout in pixel coordinates, but normalized units make the calculations easier, and allows the axes to scale more easily with the figure.
Here is how I would do it (again, borrowing inspiration from @Mathieu NOE's code):
amplitudes = [200e-3,50e-3,10e-3,200e-6];
wavelenghts = [50,500e-3,50e-3,500e-6];
ratios = (2*amplitudes)./wavelenghts;
% Allow for ~0.05% space between axes, and above and below.
onespace = 0.03;
allspace = onespace*(numel(ratios)+1);
ratios = (1-allspace).*ratios./sum(ratios);
f = figure;
top = 1;
for i = 1:numel(amplitudes)
% Make each InnerPosition full-width. The actual plot box will shrink
% the width to honor the xlim, ylim, and DataAspectRatio.
left = 0;
width = 1;
% Adjust the heights based on the ratios.
height = ratios(i);
bottom = top - onespace - height;
top = bottom;
ax = axes(f,Units='normalized',Position=[left bottom width height]);
daspect(ax, [1 1 1]);
x = 0:(wavelenghts(i)/100):(wavelenghts(i)/2);
y = amplitudes(i)*(1-cospi(2*1/wavelenghts(i)*x));
hold on
area(-x,y)
area(x,y)
xlim([-1 1]*wavelenghts(i)/2)
ylim([0 max(y)])
clear x y;
end
  4 commentaires
Benjamin Kraus
Benjamin Kraus le 2 Juil 2025
Now back to the original question (plotting four axes on top of one another with specific ratios in size).
  • In this example, we are hard-coding the xlim and ylim.
  • The requirement is that the "respective ratio of their dimensions is 1:1", which means DataAspectRatio must be [1 1 1].
  • The width of all four axes should be the same.
  • The four axes should not overlap each other (they should be stacked).
Once you've specified the limits and the data aspect ratio, then the PlotBoxAspectRatio can be derived from the other two values (or from the data directly). This is captured by the ratio in your code (and in the code I borrowed from you). In a 2D axes, the last value of PlotBoxAspectRatio doesn't really impact anything, and the first two values are used to form a ratio between x and y, which would match the value of ratio in your code.
All that is left is to ensure that the widths of all four axes are the same, and then make sure the axes are not overlapping each other, and MATLAB will take care of making sure the limits and requested DataAspectRatio are respected.
This is where the next tricky bit arrises. When you set the InnerPosition (or Position) of an axes, you are setting a bounding box, and MATLAB will draw your axes inside that bounding box. It may not fill that bounding box. If you have specified PlotBoxAspectRatio or DataAspectRatio (or both), MATLAB may shrink your axes (in either height or width) to make it fit inside the allowed space.
Considering the example above, I'm going to add an annotation rectangle that reflects the InnerPosition of the axes.
ax = axes;
ax.XLim = [0 5];
ax.YLim = [0 3];
ax.DataAspectRatio = [1 1 1];
ax.PlotBoxAspectRatio = [1 1 1];
ax.Box = 'on';
rectangle(ax, Position=[1 1 1 1]) % Unit rectangle for reference
annotation('rectangle', Position=ax.InnerPosition, Color='red')
You can see that the InnerPosition of the axes is larger than the actual plot box. That is because MATLAB needed to shrink the axes to meet the criteria (the specific limits and data aspect ratio) specified.
In my version of the code that is plotting four stacked axes:
  • I'm specifying the limits.
  • I'm specifying the DataAspectRatio should be [1 1 1] (similar to calilng axis equal).
  • Those two things combined is sufficient to restrict the PlotBoxAspectRatio to a specific value, so there is no need to set the value (it would have been ignored anyway). The beneit of not setting the PlotBoxAspectRatio is that MATLAB will calculate the value, and you can query the value to make sure it matches your expectations.
  • I'm setting the height of each axes based on the ratio, which should ensure that each axes has the correct relative height.
  • I'm setting the bottom of each axes to avoid overlapping the axes.
  • Finally, I'm setting the left and width of each axes to "fill the container", but that only controls the maximum bounding box for the plot box. Because of the other contraints, MATLAB will shrink the width of the plot box to maintain the requested limits and data aspect ratio (and effective plot box aspect ratio).
For those reasons, I'm not 100% sure why the top axes is not the same width as the others. If I can figure it out, I will add a new comment to this post.
Mathieu NOE
Mathieu NOE le 4 Juil 2025
a very big thank you for all this information - quite amazing !
all the best

Connectez-vous pour commenter.

Plus de réponses (2)

Mathieu NOE
Mathieu NOE le 30 Juin 2025
Déplacé(e) : Matt J le 30 Juin 2025
hello
well using axis equal will give you different tile widths according to your data range, so for me, there is a conflict between your requirement of having plots with same width and using axis equal
you can have the required display if you don't use axis equal (for which purpose ? :
clear variables; close all; clc;
tiledlayout(4,1)
amplitudes = [200e-3,50e-3,10e-3,200e-6];
wavelenghts = [50,500e-3,50e-3,500e-6];
for i = 1:numel(amplitudes)
nexttile
% axis equal
x = 0:(wavelenghts(i)/100):(wavelenghts(i)/2);
y = amplitudes(i)*(1-cospi(2*1/wavelenghts(i)*x));
hold on
area(-x,y)
area(x,y)
xlim([-1 1]*wavelenghts(i)/2)
% ylim([0 max(y)]) % no need here
clear x y;
end
  10 commentaires
Mathieu NOE
Mathieu NOE le 1 Juil 2025
hello again
just figured out there was a few bugs in my code :
so, hopefully, now a better code
clear variables; close all; clc;
amplitudes = [200e-3,50e-3,10e-3,200e-6];
wavelenghts = [50,500e-3,50e-3,500e-6];
ratios = (2*amplitudes)./wavelenghts;
ratios = ratios/sum(ratios);
N = numel(amplitudes);
%%%Matlab convention [left bottom width height]%%%
set(0,'Units','pixels');
scrsz = get(0,'ScreenSize');
scr_width = scrsz(3);
scr_heigth = scrsz(4);
h = figure('Units','pixels','Position',[0.1*scr_width 0.1*scr_heigth 0.8*scr_width 0.8*scr_heigth]);
top = 0.75*scr_heigth;
bottom = 0.05*scr_heigth;
vs = 0.03*scr_heigth; % vertical separation between the subplots
width = 0.1*scr_width; % initial value
height = width*ratios*scr_width/scr_heigth;
height_sum = sum(height);
available_height = top - bottom - (N-1)*vs;
correctio_factor = available_height/height_sum;
width = width*correctio_factor;
height = width*ratios*scr_width/scr_heigth;
left = (scr_width)/2 - width;
for i = 1:N
x = 0:(wavelenghts(i)/100):(wavelenghts(i)/2);
y = amplitudes(i)*(1-cospi(2*1/wavelenghts(i)*x));
s(i) = subplot(4,1,i);
hold on
area(-x,y)
area(x,y)
xlim([-1 1]*wavelenghts(i)/2)
ylim([0 2*amplitudes(i)])
pos1 = ([left (top-sum(height(1:i)) - vs*(i-1)) width height(i)]); % [left bottom width height]
set(s(i),'Units','pixels','Position',pos1);
pbaspect([1 1 1]) % Make the x-axis, y-axis equal lengths
daspect([1 1 1]); % equal lengths in all directions
clear x y;
end
Mathieu NOE
Mathieu NOE le 1 Juil 2025
and regarding tiledlayout maybe there is also a solution, but I have to admit I am still using the "old" way with subplot most of the time
I am not sure though that it's so obvious (after some searches)

Connectez-vous pour commenter.


Benjamin Kraus
Benjamin Kraus le 1 Juil 2025
You can do this with tiledlayout, but it is not really what tiledlayout was designed to do. Part of the core of the layout algorithm for tiledlayout is that each tile is given the same height/width within which to draw, but in your situation you don't want each tile to have the same height.
To get around this, you need to use tile spanning. You can tell a single axes that it should occupy multiple tiles, and that can serve as a sort of proxy for the axes height/width. However, this doesn't work perfectly for your specific scenario because tiledlayout is allocating space based on the OuterPosition (including the tick labels) not the InnerPosition (which is just the white part of the axes). This means that (for example) when you tell one axes to be 2 tiles tall, the actual white part of the axes won't necessarily be twice as tall as the one below it, but rather it is drawing using 2 tiles worth of space. For example:
tcl = tiledlayout('vertical');
ax(1) = nexttile(1, [2 1]); % Occupy 2 tiles tall and 1 tile wide.
ax(2) = nexttile(2, [1 1]); % Occupy 1 tile tall and 1 tile wide.
set(ax, Units='pixels');
vertcat(ax.InnerPosition)
ans = 2×4
73.8000 174.8556 434.0000 214.6444 73.8000 47.2000 434.0000 86.9889
<mw-icon class=""></mw-icon>
<mw-icon class=""></mw-icon>
In the output, notice that the first axes is a bit more than twice the height of the second axes, rather than being exactly twice the height. That is what I mean when I say that tiledlayout is not really designed to do this.
With all that lead-in, here is a version of your script that does something similar using tiledlayout (borrowing some code from @Mathieu NOE)
amplitudes = [200e-3,50e-3,10e-3,200e-6];
wavelenghts = [50,500e-3,50e-3,500e-6];
ratios = (2*amplitudes)./wavelenghts;
ratios = round(ratios/min(ratios));
% The first axes wasn't visible at all, so I'm skewing the data a bit here
% to make it visible.
ratios(1) = 5;
f = figure;
tcl = tiledlayout(f, 'vertical', TileSpacing='tight',Padding='compact');
for i = 1:numel(amplitudes)
ax = nexttile(tcl, i, [ratios(i) 1]);
x = 0:(wavelenghts(i)/100):(wavelenghts(i)/2);
y = amplitudes(i)*(1-cospi(2*1/wavelenghts(i)*x));
hold on
area(-x,y)
area(x,y)
xlim([-1 1]*wavelenghts(i)/2)
ylim([0 max(y)])
clear x y;
end

Catégories

En savoir plus sur Graphics Object Properties dans Help Center et File Exchange

Produits


Version

R2022a

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!

Translated by