Supported Julia: 1.10+
-
PlotGraphviz.jlbrings Graphviz rendering directly into IJulia and VS Code notebooks. -
The modern core is built on
GraphvizGraphandGraphviz_jll. For dot-file parsing it usesParserCombinator.jl. -
The Julia-side Graphviz graph representation and DOT pretty-printer are derived from Catlab.jl's Graphviz code and have been refactored and adapted for PlotGraphviz.jl.
-
PlotGraphviz.jlpresents a simple interface for (nearly) all features of Graphviz. -
PlotGraphviz.jlaccepts graphs from bothGraphs.jlandSimpleWeightedGraphs.jl. -
PlotGraphviz.jlalso provides interoperability withMetaGraphsNext.jlvia adapter functions (to_metagraph,from_metagraph).
Installation is straightforward: enter Pkg mode by hitting ], and then
Supported Julia versions: Julia 1.10 and newer.
(@v1.10) pkg> add PlotGraphvizGenerate some graphs by importing Graphs.jl or SimpleWeightedGraphs.jl:
using Graphs, SimpleWeightedGraphsusing PlotGraphvizand use SimpleWeightedGraphs.jl to generate a simple graph:
g = SimpleWeightedGraph(3) # or use `SimpleWeightedDiGraph` for directed graphs
SimpleWeightedGraphs.add_edge!(g, 1, 2, 0.5)
SimpleWeightedGraphs.add_edge!(g, 2, 3, 0.8)
SimpleWeightedGraphs.add_edge!(g, 1, 3, 2.0);plot_graphviz(g)You can use generators from Graphs.jl, i.e.:
grid = Graphs.grid([10,5]){50, 85} undirected simple Int64 graph
plot_graphviz(grid)First, let us take a standard example, and use the function read_dot_file (more with ? function) to import the graph from a dot-file.
mk, attrs = read_dot_file("./test/data/directed/clust4.gv");plot_graphviz(mk, attrs)The value
There are mainly 3 different graph options available in Graphviz (see website for more):
- graph_options: attributes/properties, which belongs to the complete graph (i.e. rankdir, label, ...)
- node_options: attributes/properties to modify all nodes at once
- edge_options: attributes/properties to modify all edges at once
As an example we would like to modify the shape of all nodes. Therefore we use the set! function. As we would like to modify the nodes, we have to use the node_options of our struct:
set!(attrs.node_options, Property("shape","box"));
plot_graphviz(mk, attrs)Next we change the orientation of our graph by modifying its graph_options and additionally we change the edge color (using edge_options):
set!(attrs.graph_options, Property("rankdir","LR"));
set!(attrs.edge_options, Property("color","blue"));
plot_graphviz(mk, attrs; scale = 5)To modify a single node, we need to access the node by its
set!(attrs.nodes, "a0", Property("shape","triangle"))
set!(attrs.nodes, "a0", Property("filled","true"))
set!(attrs.nodes, "a0", Property("color","yellow"))
plot_graphviz(mk, attrs; scale = 5)To access a single edge, we have to know its unique get_id to return the id from a node with a given name.
id_a0 = get_id(attrs.nodes,"start");
id_a1 = get_id(attrs.nodes,"a0");
set!(attrs.edges,id_a0, id_a1, Property("color","red"))
set!(attrs.edges,id_a0, id_a1, Property("xlabel","2.0"))
set!(attrs.edges,id_a0, id_a1, Property("fontsize","8.0"))
plot_graphviz(mk, attrs; scale = 5)The imported graph
set!(attrs.subgraphs[1].graph_options, Property("color","Turquoise"));
set!(attrs.subgraphs[1].graph_options, Property("label","process #NEW 1"));
plot_graphviz(mk, attrs; scale = 5)But it is not possible to access a node or edge inside a cluster. Therefore we can use a built-in trick to manipulate the node directly:
set!(attrs.subgraphs[2].nodes, "b0", Property("color","green")); ## does not work inside a cluster!
set!(attrs.nodes, "b0", Property("color","green")); ## but this works!
plot_graphviz(mk, attrs; scale = 5)To write and store the graph use the write_dot_file function:
write_dot_file(mk,"./test.dot"; attributes=attrs);PlotGraphviz.jl now includes helper APIs to map graphs and metadata between
Graphs.jl, SimpleWeightedGraphs.jl, DOT attributes, and MetaGraphsNext.jl.
Use to_weighted_graph when you start from SimpleGraph / SimpleDiGraph and
need weighted edges for plotting or export pipelines.
using Graphs, PlotGraphviz
g = SimpleDiGraph(3)
add_edge!(g, 1, 2)
add_edge!(g, 2, 3)
wg = to_weighted_graph(g; default_weight=1.0)Use apply_edge_weights to read edge weights from GraphvizAttributes.
By default, keys are checked in this order: "weight", then "xlabel".
using PlotGraphviz
g, attrs = read_dot_file("./test/data/undirected/Petersen.gv")
# Prefer "weight", then fallback to "xlabel"
wg = apply_edge_weights(g, attrs; weight_keys=("weight", "xlabel"), default_weight=1.0)Use read_dot_file_weighted if you want weighted edges directly from DOT input.
using PlotGraphviz
wg, attrs = read_dot_file_weighted("./test/data/undirected/Petersen.gv")Use to_metagraph to convert (weighted_graph, attrs) to a MetaGraph.
- graph options (
attrs.graph_options) ->mg[] - node attributes (
attrs.nodes) ->mg[label] - edge attributes (
attrs.edges) ->mg[label1, label2]
using PlotGraphviz, MetaGraphsNext
g, attrs = read_dot_file_weighted("./test/data/directed/clust4.gv")
mg = to_metagraph(g, attrs)
# Access mapped metadata
graph_meta = mg[]Use from_metagraph to reconstruct a weighted graph plus GraphvizAttributes.
using PlotGraphviz, MetaGraphsNext
# Assume `mg` is an existing MetaGraph
wg2, attrs2 = from_metagraph(mg)
plot_graphviz(wg2, attrs2)For robust roundtrips, use weight_key="weight" consistently for edge weights,
and treat "xlabel" primarily as a display label.
Back to our graph
- call an empty constuctor: attrs = GraphivzAttributes()
- call the contructor with our graph
$g$ : attrs = GraphivzAttributes(g::AbstractSimpleWeightedGraph)
The second call generates the default plotting parameter, which ist used to represent the graph using plot_graphviz().
attrs = GraphvizAttributes(g)GraphvizAttributes(Property[Property{String}("weights", "false"), Property{String}("largenet", "200")], Property[Property{String}("center", "\"1,1\""), Property{String}("overlap", "scale"), Property{String}("concentrate", "true"), Property{String}("layout", "neato"), Property{String}("size", "3.0")], Property[Property{String}("color", "Turquoise"), Property{String}("fontsize", "7.0"), Property{String}("width", "0.25"), Property{String}("height", "0.25"), Property{String}("fixedsize", "true"), Property{String}("shape", "circle")], Property[Property{String}("arrowsize", "0.5"), Property{String}("arrowtype", "normal"), Property{String}("fontsize", "1.0")], PlotGraphviz.gvSubGraph[], gvNode[gvNode(1, "1", Property[]), gvNode(2, "2", Property[]), gvNode(3, "3", Property[])], gvEdge[gvEdge(1, 2, Property[Property{Float64}("xlabel", 0.5)]), gvEdge(1, 3, Property[Property{Float64}("xlabel", 2.0)]), gvEdge(2, 1, Property[Property{Float64}("xlabel", 0.5)]), gvEdge(2, 3, Property[Property{Float64}("xlabel", 0.8)]), gvEdge(3, 1, Property[Property{Float64}("xlabel", 2.0)]), gvEdge(3, 2, Property[Property{Float64}("xlabel", 0.8)])])
There a two special functions available.
One typical problem in graph theory is to identify connected components and to color them:
g2,attrs2 = read_dot_file("./test/data/example.dot");plot_graphviz(g2; edge_label=true, scale=6)Use Graphs.jl algorithm to identify connected components:
L = Graphs.connected_components(g2)8-element Vector{Vector{Int64}}:
[1, 19]
[2, 7, 13, 16]
[3, 4, 9, 11]
[5, 6, 10, 17, 18]
[8, 20]
[12]
[14]
[15]
Transform it to a vector for which each number represents the color of node:
color_vec = zeros(Int, 1, nv(g2))
color = 1
for components in L
for idx in components
color_vec[idx] = color
end
color = color + 1
endplot_graphviz(g2, attrs2; colors = color_vec, scale = 7)Import a small layered dag:
lydag, attrs = read_dot_file("./test/data/small_layered_dag.dot");plot_graphviz(lydag; landscape = true, scale = 7)To get the shortest path, we use Graphs.jl:
path = Graphs.dijkstra_shortest_paths(lydag, 3);# convert precedessor list in path:
spath(ds, source, sink) = source == sink ? source : [spath(ds, ds.parents[source], sink) source];And evaluate shortest path between super-sink and super-source:
L= spath(path, 25, 3)1×12 Matrix{Int64}:
3 34 26 31 22 16 28 12 11 27 20 25
And that represents the shortest path in our graph, and we can visualize this by using the
plot_graphviz(lydag; landscape = true, scale = 7, path = L)- Automated tests are in place and currently passing (including the
CLI parity for test/data graphssuite). - DOT parsing still has known scope limits:
- for files containing multiple graphs, only the first graph is currently used
- error handling for malformed/unsupported DOT input can be improved
save_dot_asis not implemented yet.