geryjuhasz
geryjuhasz

Reputation: 184

Mathematic graphs with matlab gui

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

Answers (2)

Amro
Amro

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

gui

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:

  • left-click inside the axis to create vertices
  • right-click on two nodes to create an edge
  • use the listbox to select and highlight nodes. Use the "delete" button to remove the selected vertex.
  • The "clear" button resets everything
  • 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

Michele
Michele

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

Related Questions