How to make a tiled figure, in which the tiles have figures with multiple x and y axes?
16 views (last 30 days)
Show older comments
Hello,
I am attempting to generate a figure that includes multiple sub-figures. Each subplot (tile) is generated as the script loops through a different data set, with the result being a multi-tiled figure. Typically this can be done pretty easily using "subplot" (I know that is now dated), or "tiledlayout" or "nexttile". My challenge however is that I want each tile (or subplot) to incluede a figure with multiple x and y axes.
There is a nice description for generating multi-axis plots at: https://www.mathworks.com/help/matlab/creating_plots/graph-with-multiple-x-axes-and-y-axes.html. The process works fine and takes advantage of using a 1x1 tiled arangement where the user defines two axes objects in the same tile.
However, I would like to do this for an N x N tiled figure.
(some example data sets are attached at the bottom of this message)
A simple modified version of the method demonstrated in the above help file ALMOST works.
t = tiledlayout(2,2); % Note that I've changed this to a 2 x 2 tile layout
ax1 = axes(t);
scatter(ax1,x1,y1,'MarkerEdgeColor','k');
ylim(ax1,[0,25]);
xlim(ax1,[0,7]);
ylabel(ax1,'Height (m)');
xlabel(ax1,'Index Year');
ax1.LineWidth = linewidth;
ax1.FontSize = fontsize;
ax1.FontWeight = 'bold';
ax2 = axes(t);
stairs(ax2,x2,y2,'k');
ax2.YAxisLocation = 'right';
ax2.XAxisLocation = 'top';
ylim(ax2,[0,25]);
xlim(ax2,[-1000,0]);
ylabel(ax2,'Height (m)');
xlabel(ax2,'Slice Count');
ax2.Box = 'off';
ax2.Color = 'none';
ax2.LineWidth = linewidth;
ax2.FontSize = fontsize;
ax2.FontWeight = 'bold';
Running this script with the data below generates the first tile (nexttile(1)) correctly.

My challenge is, how do I repeat this process for subsequent tiles?
Things I've tried:
1) Running the process again with the same script, which obviously simply replaces the figure in tile 1 with the new data because it is grabbing the same axes objects in the same location.
2) Adding a call to nexttile during the following iteration (tile creation).
nexttile;
ax1 = nexttile(2); % So I am redefining ax1, yes, I'm recycling the variable name
scatter(ax1,x1,y1); Plot the scatter data
And the result is:

So far, so good! We have the first set of axes in the new tile position.
However
ax2 = nexttile(2);
results in the second set of axes overwriting the first set. So now I have:

and now axes object ax1 is lost.
I did try adding a hold call: This works, but now I am stuck using a single axes handle (ax1), so the two figures are combined, and I no longer have seperate axes, just one axes object.
3) I tried some other nonsense options like:
-Trying to assign an axes handle within the tiled function using ax1 = axes(nexttile(2)), which generates a "too many output arguments" error
-Trying nested calls like ax1 = axes(t,(nexttile(2)) generates error, sometimes crashes MatLab
I am hoping there is a handle function or some other way of indexing the files I am working with, so I can use multiple axes objects in each tile. Hopefully someone looks at this and finds the obvious mistakes I've made. Thanks!
SIDE NOTE: Using the nested calls for axes seems to crash out MatLab 2024.
Example Data
x1 = [3;2;1;2;NaN;4;NaN;3;NaN;3;3;4;1;1;3;4;NaN;4]
y1 = [11.5;13.53;10.75;10.85;7.22;8.26;12.74;12.31;12.64;11.12;11.88;12.95;13.65;13.71;12.16;13.16;13.68;14.26]
x2 = [10 13 11 10 5 6 10 10 25 31 46 60 61 69 71 71 72 71 70 70 77 107 131 130 150 162 185 199 250 279 297 323 315 288 258 280 277 332 325 331 325 356 357 344 316 272 306 263 257 188 153 130 101 98 53 20 11 10 10 10 16 20 18 20 20 15 10 10 10 10 10 1]
y2 = [0.3048 0.6096 0.9144 1.2192 1.5240 1.8288 2.1336 2.4384 2.7432 3.0480 3.3528 3.6576 3.9624 4.2672 4.5720 4.8768 5.1816 5.4864 5.7912 6.0960 6.4008 6.7056 7.0104 7.3152 7.6200 7.9248 8.2296 8.5344 8.8392 9.1440 9.4488 9.7536 10.0584 10.3632 10.6680 10.9728 11.2776 11.5824 11.8872 12.1920 12.4968 12.8016 13.1064 13.4112 13.7160 14.0208 14.3256 14.6304 14.9352 15.2400 15.5448 15.8496 16.1544 16.4592 16.7640 17.0688 17.3736 17.6784 17.9832 18.2880 18.5928 18.8976 19.2024 19.5072 19.8120 20.1168 20.4216 20.7264 21.0312 21.3360 21.6408 21.9456]
1 Comment
Accepted Answer
Stephen23
on 31 Jul 2025
Edited: Stephen23
on 1 Aug 2025
You can do this by setting the axes' LAYOUT.TILE property yourself, see my explanation here: https://www.mathworks.com/matlabcentral/answers/2174274-why-do-my-plots-overlap-instead-of-showing-up-side-by-side#answer_1560189
Call NEXTTILE exactly once for each tile, for all additional axes simply call AXES and set their LAYOUT.TILE property. To make your code more robust pass the TILEDLAYOUT object explicitly (which for the level of graphics you are doing you should be doing for all graphics objects anyway) to each AXES call.
x1 = [3;2;1;2;NaN;4;NaN;3;NaN;3;3;4;1;1;3;4;NaN;4];
y1 = [11.5;13.53;10.75;10.85;7.22;8.26;12.74;12.31;12.64;11.12;11.88;12.95;13.65;13.71;12.16;13.16;13.68;14.26];
x2 = [10;13;11;10;5;6;10;10;25;31;46;60;61;69;71;71;72;71;70;70;77;107;131;130;150;162;185;199;250;279;297;323;315;288;258;280;277;332;325;331;325;356;357;344;316;272;306;263;257;188;153;130;101;98;53;20;11;10;10;10;16;20;18;20;20;15;10;10;10;10;10;1];
y2 = [0.3048;0.6096;0.9144;1.2192;1.5240;1.8288;2.1336;2.4384;2.7432;3.0480;3.3528;3.6576;3.9624;4.2672;4.5720;4.8768;5.1816;5.4864;5.7912;6.0960;6.4008;6.7056;7.0104;7.3152;7.6200;7.9248;8.2296;8.5344;8.8392;9.1440;9.4488;9.7536;10.0584;10.3632;10.6680;10.9728;11.2776;11.5824;11.8872;12.1920;12.4968;12.8016;13.1064;13.4112;13.7160;14.0208;14.3256;14.6304;14.9352;15.2400;15.5448;15.8496;16.1544;16.4592;16.7640;17.0688;17.3736;17.6784;17.9832;18.2880;18.5928;18.8976;19.2024;19.5072;19.8120;20.1168;20.4216;20.7264;21.0312;21.3360;21.6408;21.9456];
t = tiledlayout(2,2);
ax1 = nexttile(t,1); % !!!
scatter(ax1,x1,y1,'MarkerEdgeColor','b');
ax2 = axes(t);
ax2.Layout.Tile = ax1.Layout.Tile; % !!!
stairs(ax2,x2,y2,'k');
ax2.Visible = 'off';
ax1 = nexttile(t,2);
scatter(ax1,x1,y1);
ax2 = axes(t);
ax2.Layout.Tile = ax1.Layout.Tile; % !!!
stairs(ax2,-x2,y2,'r');
ax2.Visible = 'off';
3 Comments
Stephen23
on 1 Aug 2025
Edited: Stephen23
on 1 Aug 2025
If you want to store all axes handles (strongly recommended):
x1 = [3;2;1;2;NaN;4;NaN;3;NaN;3;3;4;1;1;3;4;NaN;4];
y1 = [11.5;13.53;10.75;10.85;7.22;8.26;12.74;12.31;12.64;11.12;11.88;12.95;13.65;13.71;12.16;13.16;13.68;14.26];
x2 = [10;13;11;10;5;6;10;10;25;31;46;60;61;69;71;71;72;71;70;70;77;107;131;130;150;162;185;199;250;279;297;323;315;288;258;280;277;332;325;331;325;356;357;344;316;272;306;263;257;188;153;130;101;98;53;20;11;10;10;10;16;20;18;20;20;15;10;10;10;10;10;1];
y2 = [0.3048;0.6096;0.9144;1.2192;1.5240;1.8288;2.1336;2.4384;2.7432;3.0480;3.3528;3.6576;3.9624;4.2672;4.5720;4.8768;5.1816;5.4864;5.7912;6.0960;6.4008;6.7056;7.0104;7.3152;7.6200;7.9248;8.2296;8.5344;8.8392;9.1440;9.4488;9.7536;10.0584;10.3632;10.6680;10.9728;11.2776;11.5824;11.8872;12.1920;12.4968;12.8016;13.1064;13.4112;13.7160;14.0208;14.3256;14.6304;14.9352;15.2400;15.5448;15.8496;16.1544;16.4592;16.7640;17.0688;17.3736;17.6784;17.9832;18.2880;18.5928;18.8976;19.2024;19.5072;19.8120;20.1168;20.4216;20.7264;21.0312;21.3360;21.6408;21.9456];
R = 2;
C = 2;
tlh = tiledlayout(R,C);
axh = gobjects(R*C,2);
k = 1;
axh(k,1) = nexttile(tlh,k);
scatter(axh(k,1),x1,y1,'MarkerEdgeColor','b');
axh(k,2) = axes(tlh);
axh(k,2).Layout.Tile = axh(k,1).Layout.Tile;
stairs(axh(k,2),x2,y2,'k');
axh(k,2).Visible = 'off';
k = 2;
axh(k,1) = nexttile(tlh,k);
scatter(axh(k,1),x1,y1);
axh(k,2) = axes(tlh);
axh(k,2).Layout.Tile = axh(k,1).Layout.Tile;
stairs(axh(k,2),-x2,y2,'r');
axh(k,2).Visible = 'off';
dpb
on 1 Aug 2025
Edited: dpb
on 1 Aug 2025
To end up with the overall result needs a little more work that duplicates that before mimicking subplot
x1 = [3;2;1;2;NaN;4;NaN;3;NaN;3;3;4;1;1;3;4;NaN;4];
y1 = [11.5;13.53;10.75;10.85;7.22;8.26;12.74;12.31;12.64;11.12;11.88;12.95;13.65;13.71;12.16;13.16;13.68;14.26];
x2 = [10;13;11;10;5;6;10;10;25;31;46;60;61;69;71;71;72;71;70;70;77;107;131;130;150;162;185;199;250;279;297;323;315;288;258;280;277;332;325;331;325;356;357;344;316;272;306;263;257;188;153;130;101;98;53;20;11;10;10;10;16;20;18;20;20;15;10;10;10;10;10;1];
y2 = [0.3048;0.6096;0.9144;1.2192;1.5240;1.8288;2.1336;2.4384;2.7432;3.0480;3.3528;3.6576;3.9624;4.2672;4.5720;4.8768;5.1816;5.4864;5.7912;6.0960;6.4008;6.7056;7.0104;7.3152;7.6200;7.9248;8.2296;8.5344;8.8392;9.1440;9.4488;9.7536;10.0584;10.3632;10.6680;10.9728;11.2776;11.5824;11.8872;12.1920;12.4968;12.8016;13.1064;13.4112;13.7160;14.0208;14.3256;14.6304;14.9352;15.2400;15.5448;15.8496;16.1544;16.4592;16.7640;17.0688;17.3736;17.6784;17.9832;18.2880;18.5928;18.8976;19.2024;19.5072;19.8120;20.1168;20.4216;20.7264;21.0312;21.3360;21.6408;21.9456];
R = 2;
C = 2;
tlh = tiledlayout(R,C);
axh = gobjects(R*C,2);
k = 1;
axh(k,1) = nexttile(tlh,k);
scatter(axh(k,1),x1,y1,'MarkerEdgeColor','b');
xlim(axh(k,1),[0 7]);
xticks(axh(k,1),[0:2:4]);
ylim(axh(k,1),[0 25]);
xlabel(axh(k,1),'Index Year'); ylabel(axh(k,1),'Height (m)');
axh(k,2)=axes(tlh,'Color','none','YAxisLocation','right','XAxisLocation','top');
axh(k,2).XAxis.Direction='reverse'; % can't set direction at axes handle top level
hold(axh(k,2),'on') % mandatory to not lose above customizations!!!!
stairs(axh(k,2),x2,y2,'k-');
xlim(axh(k,2),[0 1000]);
xticks(axh(k,2),100*[0:2:4]);
ylim(axh(k,2),ylim(axh(k,1)));
xlabel(axh(k,2),'Slice Count'); ylabel(axh(k,2),'Height (m)');
The key is to set the background color of the overlaying axes to 'none' so the first axes is not occluded. Experimentation showed that the 'Tile' property defaults the present tile so specifically setting it doesn't seem to be needed here.
The order of the axes customization and the plotting into the axes is significant just as always; if 'hold' state is not 'on', then plot, stairs and other top-level graphics commands will reset the axes properties as well as clearing any existing plot into the axes. Otherwise, one has to be sure to put all such customizations after the plotting which could lead to unexpected consequences later if do try to use the handle later if not.
The real advantage is that the tiledlayout object is smart enough to leave room for the labels being as it is a composite object.
More Answers (1)
dpb
on 31 Jul 2025
Edited: dpb
on 1 Aug 2025
I don't think you'll be able to do this with tiledlayout. It has some additional features that subplot doesn't, but it also has some internals that aren't user-modifiable.
To use subplot one has to fix the axes individually because it is unavoidable that to try to refer to the same subplot position, the underlying existing axes is deleted; this behavior is unchangeable. Hence, the following creates the axes layout initially to record the default positions of the MxN subplots to be able to create axes at those locations that can be overlaid. The width and height are adjusted in order to provide room for the title and righthand axis labels without overwriting the other plot.
NOTA BENE: Changed the upper x-axis direction to 'reverse' so will be able to plot the actual x2,y2 data without negation.
It's unfortunate that Mathworks introduced yyaxis but didn't bother to expand it to its twin xxaxis or cousin xxyyaxis but leaves that to the user to reinvent over and over...
% some data to plot; fix up in the loop for earnest
x1 = [3;2;1;2;NaN;4;NaN;3;NaN;3;3;4;1;1;3;4;NaN;4];
y1 = [11.5;13.53;10.75;10.85;7.22;8.26;12.74;12.31;12.64;11.12;11.88;12.95;13.65;13.71;12.16;13.16;13.68;14.26];
x2 = [10 13 11 10 5 6 10 10 25 31 46 60 61 69 71 71 72 71 70 70 77 107 131 130 150 162 185 199 250 279 297 323 315 288 258 280 277 332 325 331 325 356 357 344 316 272 306 263 257 188 153 130 101 98 53 20 11 10 10 10 16 20 18 20 20 15 10 10 10 10 10 1];
y2 = [0.3048 0.6096 0.9144 1.2192 1.5240 1.8288 2.1336 2.4384 2.7432 3.0480 3.3528 3.6576 3.9624 4.2672 4.5720 4.8768 5.1816 5.4864 5.7912 6.0960 6.4008 6.7056 7.0104 7.3152 7.6200 7.9248 8.2296 8.5344 8.8392 9.1440 9.4488 9.7536 10.0584 10.3632 10.6680 10.9728 11.2776 11.5824 11.8872 12.1920 12.4968 12.8016 13.1064 13.4112 13.7160 14.0208 14.3256 14.6304 14.9352 15.2400 15.5448 15.8496 16.1544 16.4592 16.7640 17.0688 17.3736 17.6784 17.9832 18.2880 18.5928 18.8976 19.2024 19.5072 19.8120 20.1168 20.4216 20.7264 21.0312 21.3360 21.6408 21.9456];
N = 2; M = 2; % layout for subplot
% get the position vectors for those subplots
for i=1:N*M
hAx(i)=subplot(N,M,i,'Visible','off'); % create the subplot first axes
end
POS=reshape([hAx.Position],[],4).';
%POS=POS.*[1 1 0.95 0.95]; % don't need this if set OuterPosition
hAx=gobjects(M*N,2); % allocation for resulting axes handles
for i=1:N*M % now create axes at those positions to mimic subplot but overlaying second
% some data to plot; fix up in the loop for earnest
x1 = [3;2;1;2;NaN;4;NaN;3;NaN;3;3;4;1;1;3;4;NaN;4];
y1 = [11.5;13.53;10.75;10.85;7.22;8.26;12.74;12.31;12.64;11.12;11.88;12.95;13.65;13.71;12.16;13.16;13.68;14.26];
x2 = [10 13 11 10 5 6 10 10 25 31 46 60 61 69 71 71 72 71 70 70 77 107 131 130 150 162 185 199 250 279 297 323 315 288 258 280 277 332 325 331 325 356 357 344 316 272 306 263 257 188 153 130 101 98 53 20 11 10 10 10 16 20 18 20 20 15 10 10 10 10 10 1];
y2 = [0.3048 0.6096 0.9144 1.2192 1.5240 1.8288 2.1336 2.4384 2.7432 3.0480 3.3528 3.6576 3.9624 4.2672 4.5720 4.8768 5.1816 5.4864 5.7912 6.0960 6.4008 6.7056 7.0104 7.3152 7.6200 7.9248 8.2296 8.5344 8.8392 9.1440 9.4488 9.7536 10.0584 10.3632 10.6680 10.9728 11.2776 11.5824 11.8872 12.1920 12.4968 12.8016 13.1064 13.4112 13.7160 14.0208 14.3256 14.6304 14.9352 15.2400 15.5448 15.8496 16.1544 16.4592 16.7640 17.0688 17.3736 17.6784 17.9832 18.2880 18.5928 18.8976 19.2024 19.5072 19.8120 20.1168 20.4216 20.7264 21.0312 21.3360 21.6408 21.9456];
hAx=do_graph(hAx,i,POS(i,:),x1,y1,x2,y2); % draw the wanted figure, return the axes array
end
function hAx=do_graph(hAx,ix,posn,x1,y1,x2,y2)
% a function that draws the double axes in the given subplot region
hAx(ix,1)=axes('OuterPosition',posn); % the first axes
hold(hAx(ix,1),'on')
scatter(hAx(ix,1),x1,y1,'MarkerEdgeColor','k');
xlim(hAx(ix,1),[0,7]); ylim(hAx(ix,1),[0 25]);
xticks(hAx(ix,1),[0:2:4]);
xlabel(hAx(ix,1),'Index Year'); ylabel(hAx(ix,1),'Height (m)');
% do any other modifications here, removed for brevity here
% now create the overlaying axes for second plot
hAx(ix,2)=axes('Position',hAx(ix,1).Position, 'Color','none', ...
'XAxisLocation','top', 'YAxisLocation','right', ...
'Visible','on');
hAx(ix,2).XAxis.Direction='reverse';
xlim(hAx(ix,2),[0 1000]); ylim(hAx(ix,2),[0 25]);
xticks(hAx(ix,2),100*[0:2:4]);
hold(hAx(ix,2),'on')
stairs(hAx(ix,2),x2,y2,'k-');
xlabel(hAx(ix,2),'Slice Count'); ylabel(hAx(ix,2),'Height (m)');
end
4 Comments
dpb
on 31 Jul 2025
It's not how the axes handles are stored that matters, as @Stephen23 says above, what is important is that one ensures one is always addressing the correct/wanted axes for each call.
In his tiledlayout solution, in the following code snippet the first axes in a tile is created by nexttile, then the second from a direct call to axes. With tiledlayout the Layout.Tile property for this axis is set to the same value as the first axes; this is the equivalent of my code specifying the 'Position' property of the second as the same as the first; it is what causes the second to be overlaid on the first. One difference is that the tiledlayout is in the hold on state already so you'll note it wasn't used in that code.
t = tiledlayout(2,2);
ax1 = nexttile(t,1); % !!!
scatter(ax1,x1,y1,'MarkerEdgeColor','b');
ax2 = axes(t);
ax2.Layout.Tile = ax1.Layout.Tile; % !!!
...
@Stephen23 reuses the same variable for each grid in the above; my code saves them but that's not material on the creation; only that saving them lets one make additional modifications to any specific axes without having to try to locate the proper handle; just retrieve it from the array. Returning it to the caller means you can then do any further manipulations from there or anywhere else you pass them in any other code; you don't have to be in the function/script that created them; the handle knows everything there is to know about them.
See Also
Categories
Find more on 2-D and 3-D Plots in Help Center and File Exchange
Community Treasure Hunt
Find the treasures in MATLAB Central and discover how the community can help you!
Start Hunting!


