Textbox graph with NetworkLayout with labels cut off using GraphMakie

I am using a textbox graph with NetworkLayout. Using their layout algorithms it creates the positions based on the graph but when the labels are bigger than the points it cuts it of. What would be a good solution for computing the limits conserving the placement and avoiding the labels to cut off?

One could in principle write a function that solves for the limits that should be set so that all text elements are within those bounds. But because text extent is usually in screen space, this depends on axis size as well, and if the text is too large or the axis too small, there doesn’t even have to be a solution. Or the limits would have to get ridiculously wide.

I believe that is one of the issues since one can’t just adjust the limits afterwards as the positions get updated. Therefore one needs to solve the positions simultaneously.

phew, finally i got it but took me so much longer than i am willing to admit. Its not super nice and readable, I guess this can be expressed much better with the right linear algebra.

using GraphMakie
using GLMakie
using Graphs
using GeometryBasics: HyperRectangle, Point2f

g = complete_graph(3)
nlabels=["this is a super long node label",
         "this isn't",
         "but this is again, or is isn't it?\nWell it is.\nAnd also multiline!"]

fig, ax, p = graphplot(g; nlabels, layout=_->[(0,1),(1,0),(0,-1)],
                       nlabels_align=[(:right,:bottom),
                                      (:left,:center),
                                      (:left,:top)])

function update_limits!(ax)
    p = only(ax.scene.plots)
    @assert p isa GraphPlot
    nodep = get_node_plot(p)
    textp = get_nlabel_plot(p)

    to_corners = r -> (Point2f(r.origin[1:2]), Point2f(r.origin[1:2] + r.widths[1:2]))
    to_px = pts -> Makie.project(ax.scene, pts)

    # get lower left and upper right node bounding box in data and px space
    n1_dat, n2_dat = to_corners(boundingbox(nodep))
    n1, n2         = to_px(n1_dat), to_px(n2_dat)

    # get text bounding box in pixel sapce
    t1, t2 = to_corners(boundingbox(textp))

    # get axis limits in data and pixel space
    ax1_dat, ax2_dat = to_corners(ax.finallimits[])
    ax1, ax2         = to_px(ax1_dat), to_px(ax2_dat)

    # check difference between nodelabel and node bounding box in px
    px_diff1 = map(x->min(0, x), t1-n1)
    px_diff2 = map(x->max(0, x), t2-n2)

    # define "targets" in pixelspace where n1 and n2 bounding should end up
    n1_target = ax1-px_diff1 + Point2f(20, 20)
    n2_target = ax2-px_diff2 - Point2f(20, 20)

    # calculate axis limits such as bounding box of node plot goes to target
    scale = (n2_dat - n1_dat) ./ (n2_target - n1_target)
    origin = n1_dat - scale .* (n1_target - ax1)
    width = ((n2_dat - n1_dat) ./ (n2_target - n1_target)) .* (ax2 - ax1)

    xlims!(ax, origin[1], origin[1]+width[1] )
    ylims!(ax, origin[2], origin[2]+width[2] )
end

update_limits!(ax)

3 Likes