MATLAB Answers

1

How to resize figure without moving contents

Asked by Alexander Laut on 9 Oct 2018
Latest activity Edited by Alexander Laut on 11 Oct 2018
To standardized my figure export process I use a custom defined printfigs.m call at the end of each script. One of the key things it does is timestamp and add a path to each figure at the top so that I can have a record of which script generated which figure.
To prevent this from overlapping any title's, annotations, or other parts of the figure, I want to resize the figure by adding a margin to the top that I can then use to place my annotation without potentially obstructing anything in the figure. I want a code that can take any figure and stretch the margins without disturbing it's contents.
To accomplish this, I've written this little code which I've been using for the bast two years on 2016b with little issue, but now it fails in 2018b quite frequently.
function [] = resizeFig(t, l, b, r)
fh = gcf();
set(findall(fh,'-property','Units'),'Units', 'pixels'); %%Set Object Sizes to Pixels
set(fh,'position',get(fh,'position')+[0,0,l+r,t+b]); % extends range of figure only
%%Grab Re-Sizeable Objects
objs = findobj(fh,'-property','position'); % grabs all objects with position properties
oPos = get(objs,'position'); % grabs position of moveable objects
ind = cellfun(@(C) size(C,2)==4,oPos); % finds objects that take 4 vector position input
objs = objs(ind);
oPos = oPos(ind);
%%Resize Objects within Figure
nPos = cellfun(@(C) C+[l,b,0,0],oPos,'uniformoutput',false); % displace positions left and down
for i = 1:length(objs)
set(objs(i),'position',nPos{i});%+[dleft,dbot,0,0]);
end
%%Set objects back to normalized/rescaleable
set(findall(fh,'-property','Units'),'Units', 'normalized');
end
Now I do hope there is a smarter way to do this to improve performance, but more importantly the line:
oPos = get(obs,'position');
tends to fail with this error:
Error using matlab.graphics.Graphics/get
No public property 'Position' for class
'ToolbarStateButton'.
this surprises me since the previous line is purposefully chosen to define objs as only those with the position property, though it now complains that it doesn't have that property. The same occurs if I use findall instead of findobj.
What has changed since 2016b and 2018b to cause this to fail?

  0 Comments

Sign in to comment.

1 Answer

Answer by Greg
on 10 Oct 2018
Edited by Greg
on 10 Oct 2018
 Accepted Answer

Two things are actually happening. The first is somewhat nitpicky: there is no public property Position for the ToolbarStateButton. The use of findobj is allowed to see the existence of the (let's assume private) property Position, but your use of set is not allowed because it must be public to be set.
The second thing that happened is R2018b introduced some improved (?) (I think so, but you likely disagree right now) axes interaction default functionality. Most of the simple interactions are now ToolbarStateButton objects in a fancy floating-and-auto-appearing axes toolbar, instead of static buttons in the figure toolbar. These new ToolbarStateButton objects apparently have the non-public Position property.
Note: In my tests, findobj did not return the ToolbarStateButton components. I had to use findall.
To fix your code, simply delete the toolbar.
addToolbarExplorationButtons(fh); % Put the old menu buttons back just in case you need them
delete(ah.Toolbar); % ah is an axes handle in the figure
findobj(...);

  3 Comments

In my experience, it seems that findobj will only return the ToolBarStateButton if after creating an axis, the user "initiates" the toolbar via mouse-over. I think this is because the default state of 'DefaultFigureToolbar' is 'Auto'.
Basically my script will run fine in automation but can be accidently broken with an interactive user. To prevent this, I added the following to my "header" script.
set(0,'defaultfiguretoolbar','none');
But I will see if I can use your code to more explicitly handle the toolbar. Maybe it has useful features, I will have to play around with it.
Tangent
In general I'm not satisfied with the performance of resizeFig. It feels like sort of an X/Y problem in that I'm debugging finicky programs to do something I'd hope would be easier to do in MATLAB (add figure margins without disturbing internal objects).
I suspect DefaultFigureToolbar has nothing to do with it, but haven't run any tests to confirm.
There are lots of different possible approaches to this task.
  • If you're running it programmatically in line with figure generation, you could set the figure visibility to 'off' to prevent user interaction.
  • You could stick to top or right margins whereby the left and bottom of each component are good as-is after changing to units other than normalized.
  • You could create a new figure, put a panel of the original figure's size in it, and copy components into the panel.
  • Probably a lot of other really bad ideas, but they would technically do what you're attempting.
I think I found my issue, in my code elsewhere I was calling:
set(0, 'ShowHiddenHandles', 'on')
I think I wrote this into one of my codes like 3 years ago as I was playing around with hiding figures until my code was finished so my printfigs function would by default show them all before export to png.
Heres a test code I wrote that should clarify my issue:
clc;clear variables; close all
% Default Setup (Works; with or without toolbar)
% set(0,'ShowHiddenHandles','off');
% set(0,'defaultfiguretoolbar','auto');
% Alternative Setup (Works; no toolbar)
% set(0,'ShowHiddenHandles','off');
% set(0,'defaultfiguretoolbar','none');
% Alternative Setup (Fails; when toolbar initiated)
set(0,'ShowHiddenHandles','on');
set(0,'defaultfiguretoolbar','auto');
% Generate Test Scenario
figure();
axis();
fprintf('try hovering over figure with mouse to initiate toolbar...\n')
pause(3)
objs = findobj(gcf(),'-property','position'); % grabs objects with position
objs_pos = get(objs,'position'); % grabs positions of objects
disp('Completed without error!')

Sign in to comment.