tiledlayout not forming figures as intended

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

Could you shorten this down to just the relevant part for your question? When I run this I get a bunch of figures...is there a way to describe your problem in a more abstract way (e.g. with some random data, or with just one figure)? are the titles relevant?
I'm not sure I know how to interpret "tilelayout won't succesfully organize the figures into columns"...Did you want each additional tile to go in the next row of the same column? Starting in (I think R2021a) you can use the TileIndexing property to control which direction new tiles are added. Before 2021a you'll unfortunately have to do the math to figure out which is the correct tile (it's not too difficult, but please let me know if that's the bit you're stuck on)
t=tiledlayout(4,2,'TileIndexing','columnmajor');
for i = 1:8
nexttile
title(i)
end
I think this is a step in the right direction, but it did not work for me when I placed it next to the tiledlayout command. I apologize for the length of the code, but part of me was wondering if the command was in the wrong loop.
What I'd like to output is a graph very similar to yours; n number of rows with only 2 columns. However, the tiledlayout seems to only want to create plots with 3+ columns. Sorry for the confusion! I've attached a figure below that should demonstrate my problem. It mostly comes from the fact that the figures are very hard to put into a report as is.
Quick update; I threw it into my original code and it moved many of the images into m x 2 figures, so thanks!! One last thing though;
for sample 3, site 5, I'm outputting 7 plots, and they're assuming a 3x3 layout with the first 2 rows filled and the first column of the third row filled (see attached). is this due to some limit on the number of plots that can be put into a figure before the number of rows or columns changes?
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.
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)

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

I'm running into a similar problem with something like this:
for i = 1:numel(my_data_files)
% load data and plot stuff
if mod(i, 3) == 1
nexttile
hold on
% set other parameters for the new tile: ylim, title, etc.
end
end
I have specified tiledlayout(3, 1), but it's clearly ignoring that and reverting back to "flow".
No clue what's going on with my case, but I would already consider the "hold on" behavior you explained above to be a bug. Your tiledlayout parameters should not be ignored based on the hold state. If you select "hold on" without specifying a specific tile, the hold should apply to the current tile (if it exists) or the first tile upon its creation (if it doesn't). At the very least there should be a warning if you try to set the hold state without first creating a tile or figure, because there is no way someone is doing that intentionally.
The alternative to cleaning this up is that no one will use tiledlayout, because we can't trust it not to behave bizarrely if we don't coddle it, and we're right back at using subplot(), which tiledlayout was designed to improve on.
I'm sorry about your frustration with tiledlayout in this case, I don't quite have enough info from your code snippet to see where it's going wrong. I'd be happy to take a look if you can provide some minimal reproduction steps.
I get why this seems confusing - it looks like causing hold on is having an odd effect wrt. the tiledlayout object. But this isn't really what's going on - lots of functions in MATLAB will call gca if you don't specify a target: hold, plot, title, ...
If you've called tiledlayout and haven't yet created an axes in that layout, then gca doesn't create an axes in that layout, and all those functions that target gca produce some surprises. This isn't really different from sublplot - if you call hold on and then call subplot(2,2,1) the effect of calling hold is gone; if you call title and then create your first subplot then the title is gone.
The idea of the call to hold on finding an "active tiledlayout" (maybe the last one created in the current figure?) and then storing that bit of state for when the first tile is created is interesting. Would you expect other functions that target gca to do the same thing? like if you called tiledlayout; plot(1:10) would the plot wait until you've created a tile to put itself into it? I guess there's something awkward about a command putting some state into a hidden queue, but I definitely see the utility when working with something like hold or axis.
I also like the idea of finding an "active layout" so that we can find cases where you've called a function that targets gca and catch/warn in these cases...it might be a little challenging to figure out what 'active layout' means in our system, particularly given that you can have multiple tiledlayouts in a figure, and even layouts nested in layouts. We definitely see a lot of users in MATLAB calling functions like plot, title, hold before creating a figure and relying on MATLAB to create a figure and/or an axes.
Again, I totally understand the frustration and really sorry that it appears like things are behaving unpredictably. A general strategy that works around these issues is to specify explictly which axes you want to a command to target (pretty much all graphics functions that target gca by default accept an Axes object as an optional first input), and/or to specify the specific layout object you want as the first input to nexttile. That's not intended to be an excuse, MATLAB tries its best to guess where these commands should apply when not specified, but can be challenging to find a heuristic that doesn't feel surprising or broken in some circumstances.
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.
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.

Categories

Products

Release

R2021a

Asked:

on 13 Oct 2021

Edited:

on 20 Feb 2026

Community Treasure Hunt

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

Start Hunting!