Generate Graph Nodes and add buttondown callbacks for interactivity
41 views (last 30 days)
Show older comments
I'm trying to create a graph that brings up a dialogue box when one of the nodes is clicked. I use
NodeTable = table('Size',[0 4],'VariableTypes',{'double','string','double','double'}, ...
'VariableNames',{'Code','Name','Latitude','Longitude'});
EdgeTable = table('Size',[0 2],'VariableTypes',{'cell', 'double'}, ...
'VariableNames',{'EndNodes','Distance'});
nodetemp = inputdlg(["Node Name", "Latitude", "Longitude"], "Node Details");
%check input present
if isempty(nodetemp)== 0
%check node not present in table
if any(matches(app.NodeTable.Name,nodetemp(1),IgnoreCase=true))==false
%format table and add code in column 1
nodeinp = addvars(cell2table(transpose(nodetemp),'VariableNames',{'Name','Latitude','Longitude'}), app.index, 'before', 1,'NewVariableNames',{'Code'});
%update graph with node data
app.NodeTable=[app.NodeTable; nodeinp];
app.G=addnode(app.G,nodeinp);
app.map = plot(app.UIAxes,app.G,'NodeLabel',app.G.Nodes.Name,'EdgeLabel',app.G.Edges.Distance);%plot graph
%set node coordinates
app.map.XData = str2double(app.NodeTable.Longitude);
app.map.YData = str2double(app.NodeTable.Latitude);
app.index = app.index + 1; %increment node counter
end
end
to create the graph, add nodes and plot it. I then am attempting to use
nodes=findobj(app.map,'Marker','o');
nodesLength = length(nodes);
for count=1:nodesLength
nodes(count).ButtonDownFcn=@(src,event)nodeButtonDown(app,count);
end
to get a hold of the node graphical objects and assign callbacks as advised here but the plot doesn't seem to generate the nodes as children/separate objects like it says in all the documentation. The nodes variable just becomes a copy of the graphplot object. You'll notice I'm using the app designer.
0 Comments
Accepted Answer
dpb
on 21 Nov 2025 at 19:16
Edited: dpb
on 22 Nov 2025 at 18:18
Other string of comments has gotten fairly long and this is just a little different enough thought would make it a second Answer...
Experimentation shows that if one sets 'LineStyle','none' then there isn't a callback excepting for the nodes. This may not be palatable, but thought I'd throw it out there as there doesn't seem to be any other clean way to constrain the Hit coordinates to just node locations.
Either way, one could try something like
function nodeHit(hGP,e)
XYZ=[hGP.XData;hGP.YData;hGP.ZData].'; % put the data into a single array for lookup
ID=find(ismembertol(XYZ,e.IntersectionPoint,"ByRows",1)); % match the returned intersection point
if isempty(ID), warning('Hit not on Node location'), return, end
NodeColor=hGP.NodeColor; % get the initial node color
if height(NodeColor)==1, colors=repmat(NodeColor,numel(hGP.XData),1); end % create array for all nodes
colors(ID,:)=[1 0 0]; % set the selected node to red
hGP.NodeColor=colors;
% do whatever else here...
end
You would then also need a short delay and to then reset the color back to the default so don't change them permanently....there isn't a callback that will trigger when leave to use as the trigger for that...
0 Comments
More Answers (1)
dpb
on 20 Nov 2025 at 15:25
Edited: dpb
on 22 Nov 2025 at 18:11
"the plot doesn't seem to generate the nodes as children/separate objects like it says in all the documentation...."
You aren't using a scatterplot, however, you're addressing a graph which does NOT have any callback properties so your assignment only creates a field named 'ButtonDownFcn' for the nodes array, but has no bearing on there being any internal callback on a node. plot for a graph object uses one of the myriad object-specific overloaded plot functions and ends up returning a GraphPlot object which does have callbacks.
In short, instead of
...
% nodes=findobj(app.map,'Marker','o');
% nodesLength = length(nodes);
% for count=1:nodesLength
% nodes(count).ButtonDownFcn=@(src,event)nodeButtonDown(app,count);
% end
use
app.map.ButtonDownFcn=@(src,event)nodeButtonDown(app,count); % bum argument list!!!
excepting the syntax for the callback function isn't correct, either, any additional argument(s) needed after the event argument would be positional, but after event; you at the minimum need a placeholder for it if it isn't being used.
app.map.ButtonDownFcn=@(src,event,count)nodeButtonDown(app,event,count); % to have additional argument
or
app.map.ButtonDownFcn=@(src,event,count)nodeButtonDown(app,~,count); % if event isn't used.
You must always read the documentation for the specific objects in use and ensure you are addressing the proper ones that do have callbacks as assignable properties. It may be possible to use addlistener on the nodes, but that's a much more complex solution if the object desired has the inherent builtin callback.
Also, I would suggest to not use count as a variable; that aliases the builtin string function. It does get harder all the time to avoid such thing, granted...
>> which count
built-in (C:\...\...\toolbox\matlab\strfun\count)
2 Comments
dpb
on 21 Nov 2025 at 13:13
Edited: dpb
on 21 Nov 2025 at 16:05
<GraphPlot callbacks> is what is available for the object created by plot for the graph. Unless the PickableParts' is set to 'Visible' there won't be any callbacks generated.
I certainly would have thought the nodes would be part, but as the properties of the graph object itself shows, they, themselves do not have any callbacks; maybe that is an inherited behavior, but the nodes exist only as properties of the object and not as any object of their own. findnode shows that only a numeric nodeID is returned, not a handle so I don't think there's any way to actually use addlistener.
However, if I do just a trivial example locally (without the complexity of App Designer)
G=graph([1 1],[2 3]);
hGP=plot(G);
fnCB=@(s,e)disp(e);
hGP.ButtonDownFcn=fnCB;
then I do get a callback when clicking on the nodes. Unfortunately, the NodeID isn't one of the items returned, just the coordinates...
Hit with properties:
Button: 1
IntersectionPoint: [2.00 1.00 0]
Source: [1×1 GraphPlot]
EventName: 'Hit'
Hit with properties:
Button: 3
IntersectionPoint: [1.00 1.00 0]
Source: [1×1 GraphPlot]
EventName: 'Hit'
Hit with properties:
Button: 1
IntersectionPoint: [1.50 2.00 0]
Source: [1×1 GraphPlot]
EventName: 'Hit'
>>
If I look at the internals by
fnCB=@(s,e)disp(e.Source);
hGP.ButtonDownFcn=fnCB;
then
GraphPlot with properties:
NodeColor: [0 0.45 0.74]
MarkerSize: 4.00
Marker: 'o'
EdgeColor: [0 0.45 0.74]
LineWidth: 0.50
LineStyle: '-'
NodeLabel: {'1' '2' '3'}
EdgeLabel: {}
XData: [1.50 1.00 2.00]
YData: [2.00 1.00 1.00]
ZData: [0 0 0]
Use get to show all properties
>>
one does just get a copy of the object.
It does appear that one would have to locate the particular node by matching the returned IntersectionPoint to the coordinates to get the ID and then mung on that ID. Somewhat cumbersome, but it does appear it should be doable.
It also as you've discovered triggers the callback on the lines as well as the nodes and there's not enough granularity to be able to set the 'Visible' property for one and not the other. That's a pain, agreed.
I think I'd suggest it would be worthy of an enhancement request to ask for the ability to distinguish nodes from lines for callback visibility and then for the event to return the NodeID for you.
I think it is possible to make it work, but it is going to take quite a lot of machinations to do it.
This one might be worthy of a formal support request to see if the architects of the graph and graphplot have any clever things that can be done given they will know the internals.
See Also
Community Treasure Hunt
Find the treasures in MATLAB Central and discover how the community can help you!
Start Hunting!