Reputation: 184
I need to create a GUI in Matlab that enables me to draw graphs interactively, and the give values to the edges and vertices.
I then need to return these values (x, y, value) for edges and (x1, y1, x2, y2, value) for vertices.
Unfortunately I don't even know where to start. I created a gui that lets me draw lines interactively, with 2 different methods, but I don't know how to continue. Please help.
Upvotes: 0
Views: 2124
Reputation: 124543
You can always handle mouse events to enable interactive drawing. I spent some time on this and came up with the following GUI.
function interactive_graph_gui
% data
showLabels = false; % flag to determine whether to show node labels
prevIdx = []; % keeps track of 1st node clicked in creating edges
selectIdx = []; % used to highlight node selected in listbox
pts = zeros(0,2); % x/y coordinates of vertices
adj = sparse([]); % sparse adjacency matrix (undirected)
% create GUI
h = initGUI();
function h = initGUI()
h.fig = figure('Name','Interactive Graph', 'Resize','off');
h.ax = axes('Parent',h.fig, 'ButtonDownFcn',@onMouseDown, ...
'XLim',[0 1], 'YLim',[0 1], 'XTick',[], 'YTick',[], 'Box','on', ...
'Units','pixels', 'Position',[160 20 380 380]);
h.list = uicontrol('Style','listbox', 'Parent',h.fig, 'String',{}, ...
'Min',1, 'Max',1, 'Value',1, ...
'Position',[20 80 130 320], 'Callback',@onSelect);
uicontrol('Style','pushbutton', 'Parent',h.fig, 'String','Clear', ...
'Position',[20 20 60 20], 'Callback',@onClear);
uicontrol('Style','pushbutton', 'Parent',h.fig, 'String','Export', ...
'Position',[90 20 60 20], 'Callback',@onExport);
uicontrol('Style','pushbutton', 'Parent',h.fig, 'String','Delete', ...
'Position',[50 50 60 20], 'Callback',@onDelete);
h.cmenu = uicontextmenu('Parent',h.fig);
h.menu = uimenu(h.cmenu, 'Label','Show labels', 'Checked','off', ...
'Callback',@onCMenu);
set(h.list, 'UIContextMenu',h.cmenu)
h.pts = line(NaN, NaN, 'Parent',h.ax, 'HitTest','off', ...
'Marker','o', 'MarkerSize',10, 'MarkerFaceColor','b', ...
'LineStyle','none');
h.selected = line(NaN, NaN, 'Parent',h.ax, 'HitTest','off', ...
'Marker','o', 'MarkerSize',10, 'MarkerFaceColor','y', ...
'LineStyle','none');
h.prev = line(NaN, NaN, 'Parent',h.ax, 'HitTest','off', ...
'Marker','o', 'MarkerSize',20, 'Color','r', ...
'LineStyle','none', 'LineWidth',2);
h.edges = line(NaN, NaN, 'Parent',h.ax, 'HitTest','off', ...
'LineWidth',2, 'Color','g');
h.txt = [];
end
function onMouseDown(~,~)
% get location of mouse click (in data coordinates)
p = get(h.ax, 'CurrentPoint');
% determine whether normal left click was used or otherwise
if strcmpi(get(h.fig,'SelectionType'), 'Normal')
% add a new node
pts(end+1,:) = p(1,1:2);
adj(end+1,end+1) = 0;
else
% add a new edge (requires at least 2 nodes)
if size(pts,1) < 2, return; end
% hit test (find node closest to click location: euclidean distnce)
[dst,idx] = min(sum(bsxfun(@minus, pts, p(1,1:2)).^2,2));
if sqrt(dst) > 0.025, return; end
if isempty(prevIdx)
% starting node (requires a second click to finish)
prevIdx = idx;
else
% add the new edge
adj(prevIdx,idx) = 1;
prevIdx = [];
end
end
% update GUI
selectIdx = [];
redraw()
end
function onDelete(~,~)
% check that list of nodes is not empty
if isempty(pts), return; end
% delete selected node
idx = get(h.list, 'Value');
pts(idx,:) = [];
adj(:,idx) = [];
adj(idx,:) = [];
% clear previous selections
if prevIdx == idx
prevIdx = [];
end
selectIdx = [];
% update GUI
set(h.list, 'Value',max(min(idx,size(pts,1)),1))
redraw()
end
function onClear(~,~)
% reset everything
prevIdx = [];
selectIdx = [];
pts = zeros(0,2);
adj = sparse([]);
% update GUI
set(h.list, 'Value',1)
redraw()
end
function onExport(~,~)
% export nodes and adjacency matrix to base workspace
assignin('base', 'adj',(adj+adj')>0) % make it symmetric
assignin('base', 'xy',pts)
end
function onSelect(~,~)
% update index of currently selected node
selectIdx = get(h.list, 'Value');
redraw()
end
function onCMenu(~,~)
% flip state
showLabels = ~showLabels;
redraw()
end
function redraw()
% edges
p = nan(3*nnz(adj),2);
[i,j] = find(adj);
p(1:3:end,:) = pts(i,:);
p(2:3:end,:) = pts(j,:);
set(h.edges, 'XData',p(:,1), 'YData',p(:,2))
% nodes
set(h.pts, 'XData',pts(:,1), 'YData',pts(:,2))
set(h.prev, 'XData',pts(prevIdx,1), 'YData',pts(prevIdx,2))
set(h.selected, 'XData',pts(selectIdx,1), 'YData',pts(selectIdx,2))
% list of nodes
set(h.list, 'String',num2str(pts,'(%.3f,%.3f)'))
% node labels
if ishghandle(h.txt), delete(h.txt); end
if showLabels
set(h.menu, 'Checked','on')
h.txt = text(pts(:,1)+0.01, pts(:,2)+0.01, ...
num2str((1:size(pts,1))'), ...
'HitTest','off', 'FontSize',8, ...
'VerticalAlign','bottom', 'HorizontalAlign','left');
else
set(h.menu, 'Checked','off')
end
% force refresh
drawnow
end
end
It all boils down to handling the ButtonDownFcn
callback of the axis object, and querying the location of the last mouse click using the CurrentPoint
property.
Here is a list of the possible ways to interact with the GUI:
The "export" button create two variables in the base workspace containing the vertices 2D coordinates (N-by-2 matrix) and the edges (as a sparse N-by-N matrix). You can use those variables with other graph functions as usual:
gplot(adj, xy, 'b.-')
Finally you can right click on the listbox. This will bring up a popup menu, containing the option to display labels for the vertices.
You can extend the above code to assign values to vertices. For example you could use the callback function of the listbox to assign values to vertices (display an input dialog when the user selects an item from the list). You could also use the same technique shown of handling the ButtonDownFcn
callback. Similarly you could create a second listbox to display the edges and handle the assignment of values in the same manner... I will that part to you :)
Upvotes: 8
Reputation: 3061
If you want to use the mouse to draw interactively you can use the function ginput
. Here there is the manual.
I found very useful also this example.
Upvotes: 0