Constant subplot size and variable figure size

47 views (last 30 days)
I have a script that processes some surface scans. The number of files I'm processing can vary each time (from 1 to ~40). I am currently using this FE to minimize the space between each subplot.
However, I'm also outputting text on top of the surface plot, and when the number of files increases, I am forced to increase the number of rows and reduce the text size (and position) to keep everything aligned properly.
What I'd ideally like to have is a fixed size for each subplot (say, 800x800 points, font size 26 for all text annotations), and then have the figure expand beyond my screen size to accomodate more subplots -- for example, when processing 30 files I would get a 5x6 array of subplots all sized 800x800. This figure would then be exported as an image and I can resize for other uses.
I've attached an image with my intended output depending on the number of files. In each case, the individual subplot sizes and text annotations should remain the same.
  5 Comments
MH
MH on 14 Apr 2021
Thanks Rik. This is what my code looks like now, and it works relatively well.
Is there a way to optimize this so that I don't have to save 20 images? I looked into a function to montage a set of open figures, but the only one I found was this and it requires a lot of additional tweaking to get the padding/spacing right.
clear all
clc
matSize = 800;
curDir = pwd;
[X,Y,Z] = peaks(matSize);
rows = 2;
cols = 3;
for i=1:rows*cols
myFig = figure('visible','off');
set(myFig,'units','points','outerposition',[0 0 matSize matSize]);
surf(Z,'LineStyle','none');
axis equal
axis off
colormap jet
view(2)
text('Position',[0.4 0.5],'units','normalized','String','text','FontSize',32)
figStr = strcat('image',num2str(i),'.tif');
figStr = fullfile(curDir,figStr);
exportgraphics(myFig,figStr,'BackgroundColor','white','ContentType','image','Resolution',200)
close(myFig)
end
fileFolder = fullfile(pwd);
dirOutput = dir(fullfile(fileFolder,'*.tif'));
fileNames = string({dirOutput.name});
h = montage(fileNames, 'Size', [rows cols],'ThumbnailSize',[]);
montage_IM=h.CData;
imwrite(montage_IM,'montage.tif');
Rik
Rik on 14 Apr 2021
This is about what I would do.
You could improve this by moving the figure creation out of the loop and modify the properties of the graphics object inside your loop. You could also read (and delete) the image file in your loop and store the result in a 3D array. You can use permute(I stack,[1 2 4 3]) to make sure the image is the shape montage expects. (I didn't know it supported entering filenames BTW)

Sign in to comment.

Accepted Answer

MH
MH on 16 Apr 2021
Per Rik's suggestion, I've incorporated montage to produce the final desired result. If anyone has suggestions on how to further optimize this code (or another method that would produce the same result) please do chime in.
Note that the code below is just a sample snippet, so I've eliminated a lot of the user input and gui code.
matSize = 800;
curDir = pwd;
numOfFiles = 6;
[X,Y,Z] = peaks(matSize);
rows = 2;
cols = 3;
A = cell(numOfFiles);
for i=1:numOfFiles
myFig = figure('visible','off');
set(myFig,'units','points','outerposition',[0 0 matSize matSize]);
surf(Z,'LineStyle','none');
axis equal
axis off
colormap jet
view(2)
text('Position',[0.4 0.5 1],'units','normalized','String','My Text','FontSize',32)
figStr = strcat('image','.tif');
figStr = fullfile(curDir,figStr);
exportgraphics(myFig,figStr,'BackgroundColor','white','ContentType','image','Resolution',200)
A{i} = imread('image.tif');
delete('image.tif');
close(myFig);
end
figure
h = montage(A,'Size',[rows cols],'ThumbnailSize',[]);
montage_IM=h.CData;
imwrite(montage_IM,'montage.png');
  2 Comments
Rik
Rik on 16 Apr 2021
I would still move some things out of the loop. I'm not sure how much I should suggest, as this is example code, but this would be a good start.
matSize = 100;%changed for the run-in-browser
curDir = pwd;
numOfFiles = 6;
[X,Y,Z] = peaks(matSize);
rows = 2;
cols = 3;
%initialize figure
A = cell(numOfFiles);
myFig = figure('visible','off');
set(myFig,'units','points','outerposition',[0 0 matSize matSize]);
h_surf=surf(Z,'LineStyle','none');
axis equal,axis off,colormap jet,view(2)
h_txt=text('Position',[0.4 0.5 1],'units','normalized','String','My Text','FontSize',32/8);
% changed font size
figStr = strcat('image','.tif');
figStr = fullfile(curDir,figStr);
for n=1:numOfFiles
set(h_surf,'XData',X,'YData',Y,'ZData',Z)
set(h_txt,'String',sprintf('it %d',n))
drawnow;%force graphics update
exportgraphics(myFig,figStr,'BackgroundColor','white','ContentType','image','Resolution',200)
A{n} = imread(figStr);
end
delete(figStr);
close(myFig);
figure
h = montage(A,'Size',[rows cols],'ThumbnailSize',[]);
montage_IM=h.CData;
imwrite(montage_IM,'montage.png');
MH
MH on 16 Apr 2021
Thanks Rik, I'll incorporate some of the changes you suggested; it otherwise seems to be working really well for my purpose. And it's certainly more flexible than what I was doing previously (scaling font size based on the number of images being displayed).

Sign in to comment.

More Answers (1)

Adam Danz
Adam Danz on 13 Apr 2021
Edited: Adam Danz on 14 Apr 2021
I used to use that file exchange function and it has been very useful but tiledlayout is now the better option. Here's a summary updated features to tiledlayout that allows you to control spacing.
After creating the subplots, change the units from normalize to points if you want the axis sizes to stay the same when the figure size changes.
Partial demo:
fig = figure();
tlo = tiledlayout(fig,6,6,'TileSpacing','none'); % [num rows, num cols]
tileorder = [1,7,8,13,14,15,19,20,21,25:36]; % row-wise
ax = gobjects(size(tileorder));
for i = 1:numel(tileorder)
ax(i) = nexttile(tileorder(i));
end
box(ax,'on')
set(ax,'xtick',[],'ytick',[])
  3 Comments
Adam Danz
Adam Danz on 14 Apr 2021
I've updated my answer to recreate the subplot layout in the image you shared.
I'm not sure I understand the spacing issues you're describing in your comment above. I only see 1 pair of axes, too.
MH
MH on 14 Apr 2021
Sorry if my image was confusing.
What I was trying to convey is that if I have 1 file to process, the matrix will be, for example, 800x800; thus I would want the output image to be 800pts x 800pts, with text overlaid at a font size of 26.
But if the # of files I'm processing is such that 800*numOfFiles > screen resolution, what ends up happening is the tile gets resized to something smaller than 800x800 pts, and the font size of 26 is now too large and needs to be scaled.
The solution that Rik suggested with using the montage seems to work well, but it does come at the cost of having to generate a figure, output an image file, read the file to an array, delete the file, then montage everything in that array. With the tiledlayout, once the plotting is done the image can be exported once and is done.

Sign in to comment.

Products


Release

R2020b

Community Treasure Hunt

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

Start Hunting!