Generate Graph Nodes and add buttondown callbacks for interactivity

41 views (last 30 days)
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.

Accepted Answer

dpb
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...

More Answers (1)

dpb
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...."
The example uses the scatter plot array of scatter objects which do have a callback property.
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
Timothy
Timothy on 21 Nov 2025 at 11:48
Thanks for the help. I've tried just using the plot as the button down trigger and then comparing the coordinates of the click with the coordinates of the nodes but find that clicking on the nodes doesn't actually trigger the callback. Are you saying that the simplest solution is to switch off the pickable objects attribute and proceed with this method? I don't really understand what the default datatips are attached to if the nodes aren't created as their own objects. I thought that if I could just replace the datatip display with a regular callback, that would be the most simple since it would already be associated with the node in question
dpb
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.

Sign in to comment.

Categories

Find more on App Building in Help Center and File Exchange

Products


Release

R2024b

Community Treasure Hunt

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

Start Hunting!