Dependency Graph of a Package

In this example we'll plot a dependency graph of a package using RegistryInstances.jl and and a DAG layout from LayeredLayouts.jl

using CairoMakie
using GraphMakie
using Graphs
using LayeredLayouts
using RegistryInstances
using Makie.GeometryBasics
using Makie.Colors

First we need a small function which goes through the dependencies of a package and builds a SimpleDiGraph object.

function depgraph(root)
    registries=RegistryInstances.reachable_registries()
    general = registries[findfirst(x->x.name=="General", registries)]

    packages = [root]
    connections = Vector{Pair{Int,Int}}()

    for pkg in packages
        pkgidx = findfirst(isequal(pkg), packages)
        uuids = uuids_from_name(general, pkg)
        isempty(uuids) && continue

        deps = String[]
        pkginfo = registry_info(general[only(uuids)])
        version = maximum(keys(pkginfo.version_info))
        for (vrange, dep) ∈ pkginfo.deps
            if version ∈ vrange
                append!(deps, keys(pkginfo.deps[vrange]))
            end
        end
        filter!(!isequal("julia"), deps)

        for dep in deps
            idx = findfirst(isequal(dep), packages)
            if idx === nothing
                push!(packages, dep)
                idx = lastindex(packages)
            end
            push!(connections, idx => pkgidx)
        end
    end
    g = SimpleDiGraph(length(packages))
    for c in connections
        add_edge!(g, c)
    end
    return (packages, g)
end

As an example we'll plot the dependency Graph of Revise.jl because it is one of the most important packages in the Julia ecosystem but does not have a huge dependency tree.

(packages, g) = depgraph("Revise")
N = length(packages)
xs, ys, paths = solve_positions(Zarate(), g)

# we scale the y coordinates so the plot looks nice in `DataAspect()`
ys .= 0.3 .* ys
foreach(v -> v[2] .= 0.3 .* v[2], values(paths))

In GraphMakie the layout always needs to be function. So we're creating a dummy function... We will use the Edge waypoints attribute to get the graph with the least crossings.

lay = Point.(zip(xs,ys))
# create a vector of Point2f per edge
wp = [Point2f.(zip(paths[e]...)) for e in edges(g)]

# manually tweak some of the label aligns
align = [(:right, :center) for i in 1:N]
align[findfirst(isequal("Revise"), packages)]           = (:left, :center)
align[findfirst(isequal("LoweredCodeUtils"), packages)] = (:right, :top)
align[findfirst(isequal("CodeTracking"), packages)]     = (:left, :bottom)
align[findfirst(isequal("JuliaInterpreter"), packages)] = (:left, :bottom)
align[findfirst(isequal("Requires"), packages)]         = (:left, :bottom)

# shift "JuliaInterpreter" node in data space
offset = [Point2f(0,0) for i in 1:N]
offset[findfirst(isequal("JuliaInterpreter"), packages)] = Point(-0.1, 0.1)

f, ax, p = graphplot(g; layout=lay,
                     arrow_size=15,
                     edge_color=:gray,
                     nlabels=packages,
                     nlabels_align=align,
                     nlabels_distance=10,
                     nlabels_fontsize=15,
                     nlabels_offset=offset,
                     node_size=[9.0 for i in 1:N],
                     edge_width=[3 for i in 1:ne(g)],
                     waypoints=wp,
                     waypoint_radius=0.5)
ax.title = "Dependency Graph of Revise.jl"
xlims!(ax, -0.6, 5.6)
hidedecorations!(ax); hidespines!(ax); ax.aspect = DataAspect()
Example block output

If you run this example using GLMakie you can add this code to play around with the interactive features.

deregister_interaction!(ax, :rectanglezoom)
register_interaction!(ax, :nodehover, NodeHoverHighlight(p))
register_interaction!(ax, :edgehover, EdgeHoverHighlight(p))
register_interaction!(ax, :edrag, EdgeDrag(p))
register_interaction!(ax, :ndrag, NodeDrag(p))

This page was generated using Literate.jl.