Packages

Build directed graphs, query them, and compile into common diagramming languages ([d2] for now).

Current section

Files

Jump to
graphic lib graphic.ex
Raw

lib/graphic.ex

defmodule Graphic do
@moduledoc """
Build directed graphs, query them,
and compile into common diagramming languages.
[![Run in Livebook](https://livebook.dev/badge/v1/black.svg)](https://livebook.dev/run?url=https%3A%2F%2Fshare.operand.online%2Fgram%2Fgraphic%2Fdigraph.livemd)
iex> alias Graphic, as: G
iex> G.new() |> G.bridge("a", "b") |> G.edges()
[
{"a", "b", []}
]
Edges can also be assigned a label.
iex> alias Graphic, as: G
iex> G.new() |> G.bridge("a", "b", "broken!") |> G.edges()
[
{"a", "b", "broken!"}
]
"""
@moduledoc since: "0.1.2"
@dg :digraph
def new(), do: :digraph.new()
@doc "add a plain edge, and any missing nodes."
def bridge(g, a, b) do
if !(g|>@dg.vertex(a)), do: g|>@dg.add_vertex(a)
if !(g|>@dg.vertex(b)), do: g|>@dg.add_vertex(b)
g|>@dg.add_edge(a, b)
g
end
@doc "add a labeled edge, and any missing nodes."
def bridge(g, a, b, label) do
if !(g|>@dg.vertex(a)), do: g|>@dg.add_vertex(a)
if !(g|>@dg.vertex(b)), do: g|>@dg.add_vertex(b)
g|>@dg.add_edge(a, b, label)
g
end
@doc "query all edges in graph."
def edges(g) do
g
|>@dg.edges()
|>Enum.map(& g|>@dg.edge(&1))
|>Enum.map(fn {_, a, b, l} -> {a, b, l} end)
end
@doc "query all edges from a node."
def edges(g, n) do
g
|>@dg.out_edges(n)
|>Enum.map(& g|>@dg.edge(&1))
|>Enum.map(fn {_, a, b, l} -> {a, b, l} end)
end
# choose a logical order, used in rendering.
defp order(g), do: g|>:digraph_utils.topsort()
@doc ~S"""
render graph as a [d2 diagram](https://d2lang.com).
iex>alias Graphic, as: G
iex> G.new()
...> |>G.bridge("a", "b")
...> |>G.bridge("a", "c")
...> |>G.bridge("c", "d", "broken!")
...> |>G.bridge("b", "d")
...> |>G.as_d2()
\"""
a -> b
a -> c
c -> d: 'broken!'
b -> d
\"""
"""
@doc since: "0.1.2"
def as_d2(g) do
g
|>order()
|>Enum.map(& g|>edges(&1))
|>List.flatten()
|>Enum.map(fn {a, b, label} ->
"#{a} -> #{b}#{if (label != []), do: ": '#{label}'"}"
end)
|>Enum.concat([""])
|>Enum.join("\n")
end
@doc "describe neighboring nodes."
def neighbors(g, n), do: g|>@dg.out_neighbours(n)
end