tiledlayout not forming figures as intended

91 views (last 30 days)
Sam Vellequette
Sam Vellequette on 13 Oct 2021
Edited: Joshua Port on 20 Feb 2026 at 17:00
So I've been trying to develop code for a program that will automatically input data from this excel spreadsheet I've created. I would like the code to iterate over each site, for each sample, and plot the relative errors of the sample readings into error bars. The plots would then be grouped by Site, but I can't figure our why my tilelayout won't succesfully organize the figures into columns. I would also like to apologize in advance for the potential density of the code; I'm a novice, and my knowledge of good coding etiquette is pretty limited. Tips welcome lol. But a short summary of what's going on in the code down there; there are 20 potential elements in each sample, though most samples only contain around 6. Thus, I plotted each element at some number along the x-axis by having that element assigned to the variable "k".
The variable siteindexes is which rows of the cell structure are both in the sample (whose value is iterated through in the first loop) and contain a matching value within the field .Site.
I've also attached the XSLX file below. Any help in tackling this would be very appreciated.
There are also vestiges of me attempting to figure out how to make matlab automatically save the figures. I've rid of most of those, but other tips are again appreciated.
Here's the code:
%%
data=table2struct(readtable('EDS Quantitative results (1).xlsx'));
%Create field name vector and generate a new struct based on these fields
%and the data length
fnames=fieldnames(data);
lengthdata=length([data.O]);
errdata(lengthdata,1)=struct();
%Calculate percent error based on each element (Starting with Oxygen, fifth
%field)
for k=5:25
for i=1:lengthdata
errdata(i).(fnames{k})= sqrt(((5/data(i).(fnames{k}))^2)+(0.25^2)) * data(i).(fnames{k});
end
end
%% Plot by Site
fpath = 'H:\Documents\MATLAB\Figures';
figurecount=1;
for samples = 1:4
for sites = 1:max([data.Site])
samplelimitmin=find([data.Sample]==samples, 1 );
samplelimitmax=find([data.Sample]==samples, 1, 'last' );
siteindexes=find([data(samplelimitmin:samplelimitmax).Site] == sites)+samplelimitmin-1;
figure(figurecount)
if ~isempty(siteindexes)
tiledlayout(length(siteindexes),2)
for i=1:length(siteindexes)
hold on
nexttile
xlim([3 28])
ylim([0 100])
ylabel('Weight %')
labellocations=[];
labelstrings={};
count=1;
for k=5:25
hold on
if ~isnan(data(siteindexes(i)).(fnames{k}))
labellocations(count)=k;
labelstrings{count}=fnames{k};
count=count+1;
end
%If the amount of element is trace, plot as red. If not, black
if errdata(siteindexes(i)).(fnames{k}) <= 5.2
errorbar(k, data(siteindexes(i)).(fnames{k}),errdata(siteindexes(i)).(fnames{k}),'r.','MarkerSize',16,'LineWidth',1);
else
errorbar(k, data(siteindexes(i)).(fnames{k}),errdata(siteindexes(i)).(fnames{k}),'k.','MarkerSize',16,'LineWidth',1);
end
if isnumeric(data(siteindexes(i)).Spectrum) && ~isnan(data(siteindexes(i)).Spectrum)
title(strcat('Sample',{' '},num2str(samples),{' '},'Site',{' '},num2str(sites),{' '},'Spectrum',{' '},num2str(data(siteindexes(i)).Spectrum),{' '},data(siteindexes(i)).Time))
elseif isnan(data(siteindexes(i)).Spectrum)
title(strcat('Sample',{' '},num2str(samples),{' '},'Site',{' '},num2str(sites),{' '},'Spectrum',{' '},'Map 1',{' '},data(siteindexes(i)).Time))
end
set(gca,'XTick',labellocations,'XTickLabel',labelstrings)
xtickangle(45)
end
end
%if the number of plots is greater than 1, dont want to save as an
%smaller plot. So, if more than 1, full screen, if 1, small
%{
filename=string(strcat('Sample',{' '},num2str(samples),{' '},'Site',{' '},num2str(sites)));
if length(siteindexes) > 1
FigH = figure('Position', get(0, 'Screensize'));
saveas(gca, fullfile(fpath, filename), 'jpeg');
else
saveas(gca, fullfile(fpath, filename), 'jpeg');
end
%}
figurecount=figurecount+1;
else
end
end
end
  5 Comments
Dave B
Dave B on 13 Oct 2021
Edited: Dave B on 14 Oct 2021
It took me a while but I think I see where your confusing results are coming from:
tiledlayout(length(siteindexes),2)
for i=1:length(siteindexes)
hold on
nexttile
Short version: call nexttile before calling hold on, I suspect this will solve your issues.
Long version (this is a lot of explanation about a particularly unfortunate corner you landed in):
The first line, tiledlayout(length(siteindexes),2), creates a new TiledChartLayout with a specific size (two columns).
The third line, hold on, just sets the hold state. It seems pretty harmless. But here's a thing to know about MATLAB: for lot's of commands, if you don't specify a "target", MATLAB will pick one for you. Here, hold on, is equivalent to hold(gca, 'on'). gca is a magical command - it find the current axes, and if one doesn't exist, it creates one.
So after hold on runs now there's an axes, and it's not parented to the layout, it's just sort of in the same figure.
On your next line you call nexttile. nexttile has a simlar pattern to hold in this case - if you don't give it a target (which TiledChartLayout it should go into) it just picks one. And if it doesn't find a layout it will create one (and it defaults to creating one with the flow layout).
nexttile's method for picking a layout is pretty straightforward but not documented. In this case, it saw a current axes (gca) and saw that this axes wasn't in a layout, and so decided you must want a new layout. Creating the new layout in turn wiped out the axes (layouts erase things behind them).
Whew! Very particular and confusing result of calling hold on a bit early!
When you got down to plotting, sometimes your plots were arranged as expected, sometimes not. That's because the flow layout is the default when nexttile causes a new layout to be created, and because flow layout automatically picks the number of rows and columns.
Sam Vellequette
Sam Vellequette on 14 Oct 2021
This perfectly fixed it! Thanks for the explanation too. I would've definitely been left perpetually mystified as to why it was fixed.
My plots are beautiful now :")
thanks again!!

Sign in to comment.

Answers (1)

Dave B
Dave B on 14 Oct 2021
Just pasting my comment in the answer in case it may help someone else, it struck me as strange that there was a hold on before a nexttile but I didn't think twice about it, and really it causes some major confusion!
It took me a while but I think I see where your confusing results are coming from:
tiledlayout(length(siteindexes),2)
for i=1:length(siteindexes)
hold on
nexttile
Short version: call nexttile before calling hold on, I suspect this will solve your issues.
Long version (this is a lot of explanation about a particularly unfortunate corner you landed in):
The first line, tiledlayout(length(siteindexes),2), creates a new TiledChartLayout with a specific size (two columns).
The third line, hold on, just sets the hold state. It seems pretty harmless. But here's a thing to know about MATLAB: for lot's of commands, if you don't specify a "target", MATLAB will pick one for you. Here, hold on, is equivalent to hold(gca, 'on'). gca is a magical command - it find the current axes, and if one doesn't exist, it creates one.
So after hold on runs now there's an axes, and it's not parented to the layout, it's just sort of in the same figure.
On your next line you call nexttile. nexttile has a simlar pattern to hold in this case - if you don't give it a target (which TiledChartLayout it should go into) it just picks one. And if it doesn't find a layout it will create one (and it defaults to creating one with the flow layout).
nexttile's method for picking a layout is pretty straightforward but not documented. In this case, it saw a current axes (gca) and saw that this axes wasn't in a layout, and so decided you must want a new layout. Creating the new layout in turn wiped out the axes (layouts erase things behind them).
Whew! Very particular and confusing result of calling hold on a bit early!
When you got down to plotting, sometimes your plots were arranged as expected, sometimes not. That's because the flow layout is the default when nexttile causes a new layout to be created, and because flow layout automatically picks the number of rows and columns.
  4 Comments
Joshua Port
Joshua Port on 19 Feb 2026 at 21:55
Edited: Joshua Port on 20 Feb 2026 at 17:00
Thanks for the great reply, Dave. I did some more troubleshooting - the below example is basically a simple version of what I'm running into, and it seems like another weird interaction with gca like the one with hold on.
tiledlayout(3, 1)
colors = repmat(get(gca, 'colororder'), 2, 1); % Causes the problem, because axes haven't been created yet
for i = 1:9
if mod(i, 3) == 1
nexttile
hold on % Doesn't cause the problem, because axes have been created
end
if i <= 3
plot([0 i], [0 i], 'Color', colors(i, :), 'LineWidth', 2)
plot([i 2 * i], [0 2 * i], '--', 'Color', colors(i, :), 'LineWidth', 2)
elseif i <= 6
plot([0 3 * i], [0 3 * i], 'Color', colors(i - 3, :), 'LineWidth', 2)
plot([i 4 * i], [0 4 * i], '--', 'Color', colors(i - 3, :), 'LineWidth', 2)
else
plot([0 5 * i], [0 5 * i], 'Color', colors(i - 6, :), 'LineWidth', 2)
plot([i 6 * i], [0 6 * i], '--', 'Color', colors(i - 6, :), 'LineWidth', 2)
end
end
I'm not sure if there are wacky and unpleasant implications of this (breaking decades of legacy code, I'm sure), but generally, my thought it that referencing gca when a figure exists but axes on that figure don't exist yet should not break the instructions you've given Matlab when you created that figure. And doubly so when you don't explicitly reference gca and it's just happening behind the scenes within some function. Warning the user would be fine, as would more gracefully handling gca behind the scenes (i.e. making axes that still respect preferences for the figure stated earlier in the code).
As a hypothetical, imagine if I explicitly told Matlab to create a figure 250 pixels by 250 pixels, then ran "hold on" before creating my axes, and Matlab silently decided to create axes at the (much larger) default size for new figures. That's an analogous behavior in that explicit preferences for that figure would be ignored, and I'm sure you'd agree that would be silly and surely not something anyone would do on purpose.
Or, to pick a real example, imagine I've gone up to the counter at a cafe and asked for a coffee in my personal mug, and I start to reach into my bag to grab my mug. The barista, who doesn't have my mug at the time of my order despite my expressed preference, makes a coffee in a paper cup. I would much prefer they say, "Hey customer, can you give me your mug, since you asked for me to put coffee in it?" than to ignore what I asked for.
Edit: @Dave B I can't reply to your comment below because of some bizarre error ("An Error Occurred. Unable to complete the action because of changes made to the page. Reload the page to see its updated state." Reloading the page doesn't help.).
What an awesome and helpful response, though! I appreciate it. Hopefully a formal "gctl" can get added at some point (and hopefully calling gca after creating a tiledlayout in the current figure would just automatically call gctl behind the scenes!), but in the meantime, I understand what's going on and have a handful of fair workarounds.
Dave B
Dave B on 20 Feb 2026 at 3:15
Thanks Joshua, the repro steps are really helpful.
You're right that this is very much about the line of code calling where you called gca, and I agree the behavior is really confusing. I was missing the specifics in the way I characterized it above, so I wanted to give a more detailed description below. TL;DR - I think your confusion makes sense, I don't know how I'd reason through this if I wasn't a MathWorks insider...I'll do my best to capture it so if there's something that can be done to improve the software without breaking existing workflows so that you (and users in your shoes) don't run into this kind of thing. Really thank you for the details, the best way we can improve our software is when you share feedback and we really want to hear when things aren't acting how you expect so we can make it better!
The source of this is really about how nexttile identifies which tiledlayout to create an axes in. We have a gca which is "get current axes" and a gcf which is "get current figure" but no "gctl" (get current tiled layout). Internally nexttile tries to take a guess at what to target, and the rules it follows are approximately:
  1. if there's a current axes, and it's in a tiled layout, that's the one, and add an axes in the next available tile.
  2. if there isn't a current axes, look at the current figure and if there's a layout there use it.
  3. If there is a current axes, and it isn't in a tiledlayout (your case) or there's no layout in the current figure, make a tiledlayout in the current figure using the default options (flow layout, current figure). Then add an axes to that new layout.
  4. if there isn't a current figure, make one, then make a layout, then make an axes.
There's one more relevant side effect, which is that when you create a tiledlayout, it deletes axes and other tiledlayouts that are under it.
So here's what happens:
(I deleted the output because it was hard to look at with the inline figure)
t=tiledlayout(3,1); % creates a tiledlayout
ax=gca; % creates an axes, but it's not parented to t
get(gcf,'Children') % note that the axes isn't in the layout, they're both children of the figure
ax2=nexttile; % looks at gca, it's not in a layout, so calls tiledlayout
% tiledlayout deletes ax and t, and the a new axes is created
% in the new layout
t % deleted
ax % deleted
newt = get(gcf, 'Children') % the new layout
newt.Children % This is ax2
Here are a few high level strategies to work around this confusion (sorry just some thoughts I didn't spell out the specifics because it seems like a long post already!):
  • It's probably best to avoid calling gca when you don't want to create an axes. But if you need it because you want to make sure you have the correct colors for the current context (like maybe color order was already set on the figure), you could delete it when you're done. ax=axes; clr = colororder(ax); delete(ax)
  • You could prevent nexttile triggering its look for a layout behavior with t=tiledlayout(...); ... nexttile(t) (this would prevent deleting your original layout, but would still leave you with one extra axes)
  • You could skip the mod behavior by specifying the tile number in the call to nexttile, and then you could create that initial axes in the layout instead of in the figure: clr = colororder(nexttile) ...for i = 1:9 nexttile(floor(i/3+1)); hold on;...
  • Of course, you could get your colors from the axes in the loop after calling nexttile, it would be a little redundant looking
Hope this helps, and sorry again about the confusion, thanks for spelling out the details for me!

Sign in to comment.

Products


Release

R2021a

Community Treasure Hunt

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

Start Hunting!