Attributing multiple Peer objects to a single item in legend - matlab

Simplified Problem
I'm plotting 10 items.
To generate my plot I am creating 10 independent Line objects in a for loop
x=1:10;y=1;10;names = num2str((1:10)');
for i = 1:10
my_plots(i) = plot(x(i),y(i),'.','Color',rgb(i,:),'MarkerSize',14);
end
legend(my_plots,names);
When I click on an item in the legend it toggles visibility of the corresponding line
(legHandle.ItemHitFcn = toggleLegendItem).
function toggleLegendItem(src,evnt)
if strcmp(evnt.Peer.Visible,'on')
evnt.Peer.Visible = 'off';
else
evnt.Peer.Visible= 'on';
end
end
This works fine
I run into issues when some of my plots are actually the same category.
In this simple example, I could combine lines 1:5 into one object in a number of different ways, and pass the combined object to the legend as a single item in my_plots.
Actual problem
All of my line objects are distributed between several independent axes/subplots. Each axis has to remain independent because I need to be able to freely rotate each subplot without disturbing the other plots.
Progress so far
I've been able to build a shared legend between axes (because same class items share the same color, I only need to link the first line for each class), but I cannot figure out how to link multiple line objects to a single legend item, so that I am able to properly all lines across axes on each legend item callback.
I have an array of lines.
{{Line Line Line}}
{{Line Line Line}}
{{Line Line Line}}
...
{{Line Line Line}}
Basically what I need to do is set multiple Peer objects to a single legend item, but I am not sure if that is possible.
If someone has another solution that allows for combing multiple line handles across axes that would be helpful too.

So looking at the legend() function, I found that it's not possible to instantiate a legend with multiple Peer objects per item. There may be a way to go back and add multiple references via some sort of "combined object" but I'm not sure if a "combined object" exists that can handle objects with different Parent handles.
A quick fix to my problem (which was attributing a callback to the same item in multiple axes) was to use the shared properties within each class and across axes to get the handles for all items within each class. In this case I already assigned color to be distinctive between categories but a more objective property like Tag could also be used.
function toggleLegendItem(src,evnt)
% Find all items in this category
obj = findobj(findall(gcf,'type','Scatter'),'CData',evnt.Peer.CData);
for oid = 1:length(obj)
if strcmp(obj(oid).Visible,'on')
obj(oid).Visible = 'off';
else
obj(oid).Visible= 'on';
end
end
end
The one nuisance that this solution leaves is that the legend is a child of only one axis. If each category is not present in all axes, then the color for those missing categories may not show up. Luckily when you toggle each item's visibility the color appears and functions as normal. I would guess some other errors like this could occur.

Related

Why is MATLAB's legend function so slow, and how to optimize?

Problem
In a GUI I've written I've realized that the largest bottleneck in my code performance is creating/updating the legend.
Currently I delete and recreate the legend at each update of the GUI as the user needs to be able to adjust what is in the legend. I'm currently toggling what is in the legend by adjusting the setting of LINEHANDLE.Annotation.LegendInformation.IconDisplayStyle and updating the legend using legend('off');legend('show');
Testing
The following code snippet shows that the call of legend mostly is limited by the call to legend>make_legend and is fairly independent of the legend content.
close all
n=50; % number of plot lines
S=rand(n); % data
legs=cellstr(char(26*rand(n,10)+97)); % legend entries
profile on %start profiler
plot(S)
legend(legs{:})
profile viewer % view call stats
Question
Is there a better way of updating legend content without deleting it and therefore forcing it to re-call make_legend at recreation?
Furthermore I wonder if it is known why legend in general is so slow and has such odd behavior.
Purpose
I'm adding some information here to avoid the XY Problem.
A minimal example of what I'm trying to do is:
I'm building a GUI which plots four lines, let's call them data1, data2, linear model 1, and linear model 2. The data lines are independent in both color and content, while the linear models both have the same appearance and are connected to respective data line.
I want there to be a legend which only has three entries: data1, data2, and linear model. So far, no problem.
I also want there to be three toggle-buttons which toggle the visibility of the four lines on the axes and legend. The buttons are:
data1, which toggles the visibility of both the data1, and linear model 1 data lines.
data2, which toggles the visibility of both the data2, and linear model 2 data lines.
linear model, which toggles the visibility of the linear model 1, and linear model 2 data lines.
Attempted Solutions
My first approach was to first only pass three handles to legend and then have the button callbacks adjust the visibility property of the line objects according to above.
This creates the issue that when disabling the first data line and respective linear model the legend entry for linear model also blanks out as it is connected to that specific line object, even though the other is still visible.
My current working approach instead manually sets the DisplayNameproperty of all lines and then the button callbacks adjust each lines Annotation.LegendInformation.IconDisplayStyle property. According to the documentation the user then needs to call legend to force an update.
But this is not implemented, looking at the code of legend.m it is clear that this option only returns the current legend object without any other manipulation. Therefore I'm forced to call legend('off');legend('show'); which triggers a (slow) creation of a new legend object.
This currently works but using profile I can see that the legend creation is half my computation time and has a fairly large effect on user experience when using the GUI on a slower laptop. I've already ensured that my code runs legend('off');legend('show'); only if it really has to.
The question is if any user here is able to call the unreadable, yet accessible class methods of matlab.graphics.illustration.Legend to trigger an update of an existing object without forcing it to delete and recreate. Thereby doing what the MATHWORKS documentation claims to be implemented (although it is not) in legend.
Alternatively I'm open to finding a different way of changing which line objects the current legend is tracking efficiently.
You can try to change the properties of the legend object directly. Look at http://www.mathworks.com/help/matlab/ref/legend-properties.html for a list of properties. The legend object can be accessed by:
% Assign the output of legend to a variable. Do this only once at creation.
l = legend(<some arguments>);
% Example: change the location of the legend to 'north'
l.Location = 'north';
I think this is what you asked for, but I'm not sure about any efficiency gains.

Change label of data tips on bode nichols diagram

When we plot a bode/nichols locus, the name of workspace variable is used
tmp=ss(1,1,1,0);
nichols(tmp);
will use 'tmp' as label.
When using more complex data, matlab is using 'untitled1','untitled2',...
tmp={ss(1,1,1,0) , ss(1.2,1,1,0)};
nichols(tmp{:});
How can I change this label programmatically?
Ideally, I'd like a solution working with Matlab 6.5.1, but I'm also interested in solutions restricted to newer versions.
You can modify the labels programmatically via their graphics handles. It looks like the values you want to change are the DisplayName property of some of the children of the current axis. So in your first example, I can change the display name like this:
ch = get(gca,'Children');
set(ch(1),'DisplayName','Fred');
In general, I'm not sure how to predict which children of the current axis are the ones you need to change. For the second example you give, the two curves appear to be the second and third children when I run your code.

Using handles returned with findobj [duplicate]

I already have the functions required to drag and drop a single box in a figure in MATLAB. The code I wrote fills the figure with several boxes. With another loop I filled the figure with more boxes (which hold different information in string form).
These two sets of boxes are related by the numbers I placed in their UserData (corresponding numbers; for each box, there's another with the same UserData content). By finding boxes containing the same UserData (and thus relating them) I want to be able to relocate a member of the first set of boxes to the same position relative to the corresponding member of the second set of boxes, by means of right clicking on the box I just dragged (uicontextmenu).
function recallfcn(hObject,eventdata)
for ydx=1:2
diag_detail=get(gco,'UserData'); % This line should be in the drag fcn
diag_pos=get(gco,'Position'); % So should this one (for current objects)
xvar=diag_pos(1,1);
yvar=diag_pos(1,2);
detail=[diag_detail ydx];
set(findobj('UserData',detail),'Position',[xvar+(ydx-1.5) yvar+0.5 0.8 0.8]);
end
end
% ydx is only there to add another level of detail as I'm actually looking to move
% two boxes of the 'first kind', each of which have 2 numbers in user data, the first
% number being the same, and the second number distinguishing the first box from the
% second. The premise is the same.
I usually use findall instead of findobj, in case the handles of the objects are not visible from the outside. Other than that I don't see why your code wouldn't work.
Here's an example:
%# make a figure with two buttons, same userData
fh=figure,
uicontrol('userdata',[2 3],'parent',fh)
uicontrol('userData',[2 3],'units','normalized','position',[0.5 0.5,0.1 0.1],'parent',fh)
%# change color to red
set(findall(fh,'userData',[2 3]),'backgroundcolor','r')
%# move to the same position
set(findall(fh,'userData',[2 3]),'position',[0.3,0.3,0.1,0.1])
As Jonas alludes to, the 'HandleVisibility' property of an object will determine if the object shows up in its parent's list of children, and thus if it will be returned by functions like FINDOBJ. The standard fix is to use the function FINDALL instead.
However, the 'HandleVisibility' property also comes into play in determining whether or not an object can become the current object (i.e. returnable by the function GCO). If it is set to 'off', then that object can't become the current object. Additionally, if the 'HandleVisibility' property of the parent figure of an object is set to 'off' then none of its children (including said object) can become the current object.
If 'HandleVisibility' is set to 'on' or 'callback' for all your objects and figures, then I think everything should work fine.
you should inverse the ordre of x and y vector, and you can use just one loop, the changment in your code is :
x2=x(end:-1:1); % invers the ordre
y2=y(end:-1:1);
for i=1:length(x)
set(hLine,'xdata',x(i),'ydata',y(i)); % move the point using set
% to change the cooridinates.
set(hLine2,'xdata',x2(i),'ydata',y2(i));
M(i)=getframe(gcf);
end

Moving multiple boxes in figure?

I already have the functions required to drag and drop a single box in a figure in MATLAB. The code I wrote fills the figure with several boxes. With another loop I filled the figure with more boxes (which hold different information in string form).
These two sets of boxes are related by the numbers I placed in their UserData (corresponding numbers; for each box, there's another with the same UserData content). By finding boxes containing the same UserData (and thus relating them) I want to be able to relocate a member of the first set of boxes to the same position relative to the corresponding member of the second set of boxes, by means of right clicking on the box I just dragged (uicontextmenu).
function recallfcn(hObject,eventdata)
for ydx=1:2
diag_detail=get(gco,'UserData'); % This line should be in the drag fcn
diag_pos=get(gco,'Position'); % So should this one (for current objects)
xvar=diag_pos(1,1);
yvar=diag_pos(1,2);
detail=[diag_detail ydx];
set(findobj('UserData',detail),'Position',[xvar+(ydx-1.5) yvar+0.5 0.8 0.8]);
end
end
% ydx is only there to add another level of detail as I'm actually looking to move
% two boxes of the 'first kind', each of which have 2 numbers in user data, the first
% number being the same, and the second number distinguishing the first box from the
% second. The premise is the same.
I usually use findall instead of findobj, in case the handles of the objects are not visible from the outside. Other than that I don't see why your code wouldn't work.
Here's an example:
%# make a figure with two buttons, same userData
fh=figure,
uicontrol('userdata',[2 3],'parent',fh)
uicontrol('userData',[2 3],'units','normalized','position',[0.5 0.5,0.1 0.1],'parent',fh)
%# change color to red
set(findall(fh,'userData',[2 3]),'backgroundcolor','r')
%# move to the same position
set(findall(fh,'userData',[2 3]),'position',[0.3,0.3,0.1,0.1])
As Jonas alludes to, the 'HandleVisibility' property of an object will determine if the object shows up in its parent's list of children, and thus if it will be returned by functions like FINDOBJ. The standard fix is to use the function FINDALL instead.
However, the 'HandleVisibility' property also comes into play in determining whether or not an object can become the current object (i.e. returnable by the function GCO). If it is set to 'off', then that object can't become the current object. Additionally, if the 'HandleVisibility' property of the parent figure of an object is set to 'off' then none of its children (including said object) can become the current object.
If 'HandleVisibility' is set to 'on' or 'callback' for all your objects and figures, then I think everything should work fine.
you should inverse the ordre of x and y vector, and you can use just one loop, the changment in your code is :
x2=x(end:-1:1); % invers the ordre
y2=y(end:-1:1);
for i=1:length(x)
set(hLine,'xdata',x(i),'ydata',y(i)); % move the point using set
% to change the cooridinates.
set(hLine2,'xdata',x2(i),'ydata',y2(i));
M(i)=getframe(gcf);
end

How do I use TeX/LaTeX formatting for custom data tips in MATLAB?

I'm trying to annotate a polar plot with data tips labelled with 'R:...,Theta:...' where theta is actually the Greek symbol, rather than the word spelled out. I'm familiar with string formatting using '\theta' resulting in the symbol, but it doesn't work in this case. Is there a way to apply the LaTeX interpreter to data tips? Here's what I have so far:
f1=figure;
t=pi/4;
r=1;
polar(t,r,'.');
dcm_obj = datacursormode(f1);
set(dcm_obj,'UpdateFcn',#polarlabel)
info_struct = getCursorInfo(dcm_obj);
datacursormode on
where polarlabel is defined as follows:
function txt = polarlabel(empt,event_obj)
pos = get(event_obj,'Position');
x=pos(1);
y=pos(2);
[th,r]=cart2pol(x,y);
txt = {['R: ',num2str(r)],...
['\Theta: ',num2str(th*180/pi)]};
Update: This solution is primarily applicable to versions R2014a and older, since it appears to fail for newer versions, specifically R2014b and newer using the new handle graphics system. For newer versions using the new handle graphics system, a solution can be found here.
For some odd reason, the data cursor tool in MATLAB forcibly sets the data tip text to be displayed literally instead of with TeX/LaTeX interpreting (even if the default MATLAB settings say to do so). There also appears to be no way of directly setting text properties via the data cursor mode object properties.
However, I've figured out one workaround. If you add the following to the end of your polarlabel function, the text should display properly:
set(0,'ShowHiddenHandles','on'); % Show hidden handles
hText = findobj('Type','text','Tag','DataTipMarker'); % Find the data tip text
set(0,'ShowHiddenHandles','off'); % Hide handles again
set(hText,'Interpreter','tex'); % Change the interpreter
Explanation
Every graphics object created in the figure has to have a handle. Objects sometimes have their 'HandleVisibility' property set to 'off', so their handles won't show up in the list of child objects for their parent object, thus making them harder to find. One way around this is to set the 'ShowHiddenHandles' property of the root object to 'on'. This will then allow you to use findobj to find the handles of graphics objects with certain properties. (Note: You could also use findall and not worry about the 'ShowHiddenHandles' setting)
Turning on data cursor mode and clicking the plot creates an hggroup object, one child of which is the text object for the text that is displayed. The above code finds this text object and changes the 'Interpreter' property to 'tex' so that the theta symbol is correctly displayed.
Technically, the above code only has to be called once, not every time polarlabel is called. However, the text object doesn't exist until the first time you click on the plot to bring up the data tip (i.e. the first time polarlabel gets called), so the code has to go in the UpdateFcn for the data cursor mode object so that the first data tip displayed has the right text formatting.