legendflex-pkg
legendflex-pkg copied to clipboard
resize callbacks broken after save/load .fig
I'm using legendflex in thousands of programmatically generated figures; I typically save both a matlab .fig and a .png for each, but sometimes the png ends up being inappropriately sized for its end use. In that case, I'll load the fig and reprint it as needed. I discovered by accident that when loading/resizing a saved .fig file, my legendflex-es were not moving relative to their resized anchor objects. On a figure resize event, updatelegfigresize did get called but not updatelegpos, so the legend wasn't moving.
There might be a better way to do this, but my quick fix here was to add a subfunction duplicating your listener creation
function resetListeners(src,evt, hg2flag, Lf,hnew)
addlistener(hnew.leg, 'Position', 'PostSet', @(src,evt) updatelegappdata(src,evt,hnew.leg));
if hg2flag && strcmp(Lf.ref.Type, 'figure')
addlistener(Lf.ref, 'SizeChanged', @(src,evt) updatelegpos(src,evt,hnew.leg));
else
addlistener(Lf.ref, 'Position', 'PostSet', @(src,evt) updatelegpos(src,evt,hnew.leg));
end
and then tie that function into the figure CreateFcn callback (duplicating your logic from the ResizeFcn case)
createfcn = get(figh, 'CreateFcn');
ff = @(src,evt)resetListeners(src,evt, hg2flag, Lf,hnew);
if isempty(createfcn)
set(figh, 'CreateFcn', ff);
else
if ~iscell(createfcn)
createfcn={createfcn};
end
hasprev = cellfun(@(x) isequal(x, ff), createfcn);
if ~hasprev
createfcn = {createfcn{:} ff};
set(figh, 'CreateFcn', {@wrapper, createfcn})
end
end
I haven't tested this extensively, but it seems to work as intended on 2016a.
Thanks for this extremely useful piece of code!
The CreateFcn hook above did not quite work. In a case where I was repeatedly resetting/reusing the same figure handle (calling clf) and had a separate CreateFcn already present, I ended up getting a deep, repeated nesting of the handle array. clf
apparently does not clear the callback functions. After the logic executes once, @wrapper
is present as the first element of the xFcn cell array and the target function handle is buried in element 2, which is itself a cell array. Additionally, my anonymous function handle ff
needs to be recreated each time to update the buried references to hg2flag
, Lf
, and hnew
.
I think the below will handle all of the valid cases for an already-present CreateFcn (function handle, cell array w/first element as function handle, or string valid for eval
). There's probably a cleaner solution, but this is working for me.
createfcn = get(figh, 'CreateFcn');
ff = @(src,evt)resetListeners(src,evt, hg2flag, Lf,hnew);
if isempty(createfcn)
set(figh, 'CreateFcn', ff);
else
if numel(createfcn)==1
if ischar(createfcn)
createfcn = {@(src,evt)eval(createfcn)};
elseif isa(createfcn,'function_handle')
hasprev = strcmp(func2str(createfcn),func2str(ff));
createfcn = {createfcn};
else
% invalid existing data
end
if ~hasprev
createfcn = [createfcn(:)' {ff}];
set(figh, 'CreateFcn', {@wrapper, createfcn})
end
elseif iscell(createfcn) && isequal(createfcn{1},@wrapper) && (numel(createfcn)==2)
ff_ = func2str(ff);
hasprev = cellfun(@(x) strcmp(func2str(x),ff_), createfcn{2});
if any(hasprev)
createfcn{2}{hasprev} = ff;
else
%not sure how one would get here, but...
createfcn{2}{end+1} = ff;
end
set(figh, 'CreateFcn', createfcn)
else
% invalid existing data
end
end
Your already-present hasprev check for the ResizeFcn might present my first issue in certain scenarios...I think the @wrapper
entry will always throw off that check. I'd suggest something like
hasprev = isequal(rsz,{@updatelegfigresize}) || ...
((numel(rsz)==2) && isequal(rsz{1},@wrapper) && any(cellfun(@(x) isequal(x,@updatelegfigresize),rsz{2})));
One last edit...the above missed recreating the function handle for single-CreateFcn cases in reused figures.
createfcn = get(figh, 'CreateFcn');
ff = @(src,evt)resetListeners(src,evt, hg2flag, Lf,hnew);
if isempty(createfcn)
set(figh, 'CreateFcn', ff);
else
if numel(createfcn)==1
if ischar(createfcn)
createfcn = {@(src,evt)eval(createfcn)};
elseif isa(createfcn,'function_handle')
hasprev = strcmp(func2str(createfcn),func2str(ff));
createfcn = {createfcn};
else
% invalid existing data
end
if ~hasprev
createfcn = [createfcn(:)' {ff}];
set(figh, 'CreateFcn', {@wrapper, createfcn})
else
set(figh, 'CreateFcn', ff)
end
elseif iscell(createfcn) && isequal(createfcn{1},@wrapper) && (numel(createfcn)==2)
ff_ = func2str(ff);
hasprev = cellfun(@(x) strcmp(func2str(x),ff_), createfcn{2});
if any(hasprev)
createfcn{2}{hasprev} = ff;
else
%not sure how one would get here, but...
createfcn{2}{end+1} = ff;
end
set(figh, 'CreateFcn', createfcn)
else
% invalid existing data
end
end
Thanks for bringing this to my attention. I'll play around with your code suggestion in my test cases and merge it into the main branch if all works well.
Hi again...two more scenarios. If there is more than one legendflex in a figure, my kludge only worked for the most recently added, and, regardless of the number of legends, the callbacks/listeners are non-functional after copyobj
on the parent figure because appdata is not copied.
For the first issue, the problem stemmed from using func2str
to determine if the resetListeners
callback was already present--under that scenario, I was using a single figure handle to plot, clf
, repeat...so after clf
, the prior callback would be broken but "look" like the one I wanted only one of. I've removed this shallow duplicate check altogether, and will have to just remember to manually clear out these figure callbacks after clf
.
createfcn = get(figh, 'CreateFcn');
ff = @(src,evt)resetListeners(src,evt, hg2flag, Lf,hnew);
if isempty(createfcn)
set(figh, 'CreateFcn', ff);
else
if numel(createfcn)==1
if ischar(createfcn)
createfcn = {@(src,evt)eval(createfcn)};
elseif isa(createfcn,'function_handle')
createfcn = {createfcn};
else
% invalid existing data
end
createfcn = [createfcn(:)' {ff}];
set(figh, 'CreateFcn', {@wrapper, createfcn})
elseif iscell(createfcn) && isequal(createfcn{1},@wrapper) && (numel(createfcn)==2)
createfcn{2}{end+1} = ff;
set(figh, 'CreateFcn', createfcn)
else
% invalid existing data
end
end
Without doing more digging, I don't have a good suggestion for the copyobj
issue. It feels like all of these issues would go away if the legendflex data lived somewhere within the actual graphics object tree, but I'm not familiar enough with the graphics system to understand when/if matlab will translate handle references within the copy process.