defmodule Graphic do @moduledoc """ Build directed graphs, query them, and compile into common diagramming languages. iex> alias Graphic, as: G ...> G.new() ...> |> G.bridge("a", "b") ...> |> G.edges() [ {[:"$e" | 0], "a", "b", []} ] iex> alias Graphic, as: G ...> G.new() ...> |> G.bridge("a", "b", "broken!") ...> |> G.edges() [ {[:"$e" | 0], "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)) # choose a logical order, used in rendering. defp order(g), do: g|>:digraph_utils.topsort() @doc ~S""" render graph as a d2 diagram. 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(fn n -> g |>@dg.out_edges(n) |>Enum.map(& g|>@dg.edge(&1)) |>Enum.map(fn {_, a, b, label} -> "#{a} -> #{b}#{if (label != []), do: ": '#{label}'"}" end) |> Enum.sort() # g|>neighbors(&1)|>Enum.map(fn n -> # {_, ^&1, ^n, edge} = g|>edge(&1, n) # "#{&1} -> #{n}#{if edge, do: ": '#{edge}'"}" # end) end) |>List.flatten() |>Enum.concat([""]) |>Enum.join("\n") end @doc "describe neighboring nodes." def neighbors(g, n), do: g|>@dg.out_neighbours(n) end