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
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
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[]
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[]
eclick = EdgeClickHandler(edge_click_action)
register_interaction!(ax, :eclick, eclick)

Drag interactions

function node_drag_action(state, idx, event, axis)
    p[:node_pos][][idx] =
    p[:node_pos][] = p[:node_pos][]
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)
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 =
            action.src = p[:node_pos][][edge.src]
            action.dst = p[:node_pos][][edge.dst]
        offset = - 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
edrag = EdgeDragHandler(EdgeDragAction())
register_interaction!(ax, :edrag, edrag)

This page was generated using Literate.jl.