Add interactions to your graph plot

In this example you will see, how to register interactions with your graph plot. This tutorial will make use of the more basic Interaction Interface. If you just want to move nodes check out the Predefined Interactions. The implementation of those is quit similar to what is shown in this tutorial.

We star with a simple wheel graph again. This time we use arrays for some attributes because we want to change them later in the interactions for individual nodes/edges.

using CairoMakie
using GraphMakie
using Graphs
using CairoMakie.Colors

g = wheel_graph(10)
f, ax, p = graphplot(g,
                     edge_width = [2.0 for i in 1:ne(g)],
                     edge_color = [colorant"gray" for i in 1:ne(g)],
                     node_size = [10 for i in 1:nv(g)],
                     node_color = [colorant"red" for i in 1:nv(g)])
hidedecorations!(ax); hidespines!(ax)
ax.aspect = DataAspect()

Later on we want to enable drag interactions, therefore we disable the default :rectanglezoom interaction

deregister_interaction!(ax, :rectanglezoom)

Hover interactions

At first, let's add some hover interaction for our nodes using the NodeHoverHandler constructor. We need to define a action function with the signature fun(state, idx, event, axis). We use the action to make the nodes bigger on hover events.

function node_hover_action(state, idx, event, axis)
    p.node_size[][idx] = state ? 20 : 10
    p.node_size[] = p.node_size[] # trigger observable
end
nhover = NodeHoverHandler(node_hover_action)
register_interaction!(ax, :nhover, nhover)

Please run the script locally with GLMakie.jl if you want to play with the Graph 🙂 The edge hover interaction is quite similar:

function edge_hover_action(state, idx, event, axis)
    p.edge_width[][idx]= state ? 5.0 : 2.0
    p.edge_width[] = p.edge_width[] # trigger observable
end
ehover = EdgeHoverHandler(edge_hover_action)
register_interaction!(ax, :ehover, ehover)

Click interactions

In a similar fashion we might change the color of nodes and lines by click.

function node_click_action(idx, args...)
    p.node_color[][idx] = rand(RGB)
    p.node_color[] = p.node_color[]
end
nclick = NodeClickHandler(node_click_action)
register_interaction!(ax, :nclick, nclick)

function edge_click_action(idx, args...)
    p.edge_color[][idx] = rand(RGB)
    p.edge_color[] = p.edge_color[]
end
eclick = EdgeClickHandler(edge_click_action)
register_interaction!(ax, :eclick, eclick)

Drag interactions

function node_drag_action(state, idx, event, axis)
    p[:node_pos][][idx] = event.data
    p[:node_pos][] = p[:node_pos][]
end
ndrag = NodeDragHandler(node_drag_action)
register_interaction!(ax, :ndrag, ndrag)

The last example is not as straight forward. By dragging an edge we want to change the positions of both attached nodes. Therefore we need some more state inside the action. We can achieve this with a callable struct.

mutable struct EdgeDragAction
    init::Union{Nothing, Point2f} # save click position
    src::Union{Nothing, Point2f}  # save src vertex position
    dst::Union{Nothing, Point2f}  # save dst vertex position
    EdgeDragAction() = new(nothing, nothing, nothing)
end
function (action::EdgeDragAction)(state, idx, event, axis)
    edge = collect(edges(g))[idx]
    if state == true
        if action.src===action.dst===action.init===nothing
            action.init = event.data
            action.src = p[:node_pos][][edge.src]
            action.dst = p[:node_pos][][edge.dst]
        end
        offset = event.data - action.init
        p[:node_pos][][edge.src] = action.src + offset
        p[:node_pos][][edge.dst] = action.dst + offset
        p[:node_pos][] = p[:node_pos][] # trigger change
    elseif state == false
        action.src = action.dst = action.init =  nothing
    end
end
edrag = EdgeDragHandler(EdgeDragAction())
register_interaction!(ax, :edrag, edrag)

This page was generated using Literate.jl.