Packages

Dotx is a full feature library for DOT file parsing and generation. The whole spec [https://www.graphviz.org/doc/info/lang.html](https://www.graphviz.org/doc/info/lang.html) is implemented.

Current section

Files

Jump to
dotx lib helpers.ex
Raw

lib/helpers.ex

defmodule Dotx.Helpers do
# recursive Edge.flatten
def flatten(%Dotx.Graph{children: children}=graph) do
%{graph|children: Enum.flat_map(children,&flatten(&1))}
end
def flatten(%Dotx.SubGraph{children: children}=graph) do
[%{graph|children: Enum.flat_map(children,&flatten(&1))}]
end
def flatten(%Dotx.Edge{}=e) do Dotx.Edge.flatten(e) end
def flatten(other) do [other] end
def spread_attributes(graph) do
%{graph|children: Enum.map(graph.children,&spread_attributes(&1,graph))}
end
def spread_attributes(%Dotx.SubGraph{children: children}=e,graph) do
graph = %{graph|nodes_attrs: Map.merge(graph.nodes_attrs,e.nodes_attrs),
edges_attrs: Map.merge(graph.edges_attrs,e.edges_attrs),
graphs_attrs: Map.merge(graph.graphs_attrs,e.graphs_attrs)}
%{e|attrs: Map.merge(graph.graphs_attrs,e.attrs),
children: Enum.map(children,&spread_attributes(&1,graph))}
end
def spread_attributes(%Dotx.Edge{attrs: attrs,from: from, to: to}=e,graph) do
%{e|attrs: Map.merge(graph.edges_attrs,attrs),
from: spread_attributes(from,graph), to: spread_attributes(to,graph)}
end
def spread_attributes(%Dotx.Node{attrs: attrs}=e,graph) do
%{e|attrs: Map.merge(graph.nodes_attrs,attrs)}
end
def identify(graph) do {g,_} = identify(graph,0); g end
def identify(%{id: id, children: children}=graph,i) do
{graph,i} = case id do nil-> {%{graph| id: id || "x#{i}"},i+1}; _-> {graph,i} end
{backchildren,i} = Enum.reduce(children,{[],i},fn e, {acc,i}->
{e,i} = identify(e,i)
{[e|acc],i}
end)
{%{graph|children: Enum.reverse(backchildren)},i}
end
def identify(%Dotx.Edge{}=e,i) do
{from,i} = identify(e.from,i)
{to,i} = identify(e.to ,i)
{%{e|from: from, to: to},i}
end
def identify(o,i) do {o,i} end
def to_nodes(graph) do
graph = graph |> identify() |> flatten()
res = to_nodes(graph,%{nodes: %{},graphs: %{},parent_graph: {nil,0},
nodes_attrs: %{}, edges_attrs: %{}, graphs_attrs: %{}})
nodes = Enum.into(res.nodes,%{},fn {k,n}->
attrs = %{n.attrs|"graph"=> elem(n.attrs["graph"],0)} # remove graph depth
attrs = Map.put_new(attrs,"edges_from",[]) # if no edges, add attribute edges_from to empty
{k,%{n|attrs: attrs}}
end)
{nodes,res.graphs}
end
def to_nodes(%{children: children}=g,%{parent_graph: {parent,depth}}=acc) do
## put graph to db: add parent and children attributes, inherit attributes
g = %{g|children: [],attrs: Enum.into([
{"parent",parent},
{"children",for %Dotx.SubGraph{id: childid}<-children do childid end}
],Map.merge(acc.graphs_attrs,g.attrs))}
acc = %{acc|graphs: Map.put(acc.graphs,g.id,g)}
nodes_attrs = Map.merge(acc.nodes_attrs,g.nodes_attrs)
edges_attrs = Map.merge(acc.edges_attrs,g.edges_attrs)
graphs_attrs = Map.merge(acc.graphs_attrs,g.graphs_attrs)
Enum.reduce(children,acc,fn e,acc->
to_nodes(e,%{acc|nodes_attrs: nodes_attrs, edges_attrs: edges_attrs, graphs_attrs: graphs_attrs,
parent_graph: {g.id,depth+1}})
end)
end
def to_nodes(%Dotx.Edge{from: %Dotx.Node{}=from, to: %Dotx.Node{}=to}=e,%{parent_graph: {parent,_}}=acc) do
e = %{e|attrs: acc.edges_attrs |> Map.merge(e.attrs) |> Map.put("graph",parent)}
acc = to_nodes(to,to_nodes(from,acc))
%{acc|nodes: Map.update!(acc.nodes,from.id,fn oldn->
%{oldn|attrs: Map.update(oldn.attrs,"edges_from",[e],&[e|&1])}
end)}
end
def to_nodes(%Dotx.Node{}=n,%{parent_graph: {_,parentdepth}=pgraph}=acc) do
n = %{n|attrs: Map.merge(acc.nodes_attrs,n.attrs)}
default_n = %{n|attrs: Map.put(n.attrs,"graph",pgraph)}
%{acc|nodes: Map.update(acc.nodes,n.id,default_n,fn oldn->
n = %{oldn|attrs: Map.merge(oldn.attrs,n.attrs)}
%{n|attrs: Map.update(n.attrs,"graph",acc.parent_graph, fn {_,depth}=graph->
if parentdepth > depth do pgraph else graph end
end)}
end)}
end
def to_edges(%Dotx.Graph{}=g) do g |> to_nodes() |> to_edges() end
def to_edges({nodes,graphs}) do
edges = Enum.flat_map(nodes,fn {_,%Dotx.Node{attrs: attrs}}->
Enum.map(attrs["edges_from"],fn %{from: from, to: to}=e->
%{e|from: del_edges_from(nodes[from.id]), to: del_edges_from(nodes[to.id])}
end)
end)
{edges,graphs}
end
def del_edges_from(n) do %{n|attrs: Map.delete(n.attrs,"edges_from")} end
def to_digraph(%Dotx.Graph{}=dot) do g=:digraph.new ; fill_digraph(g,flatten(dot)); g end
def fill_digraph(g,%{children: children}) do for e<-children do fill_digraph(g,e) end end
def fill_digraph(g,%Dotx.Node{id: id}) do :digraph.add_vertex(g,id) end
def fill_digraph(g,%Dotx.Edge{from: %Dotx.Node{id: fromid}, to: %Dotx.Node{id: toid}}) do
:digraph.add_vertex(g,fromid)
:digraph.add_vertex(g,toid)
:digraph.add_edge(g,fromid,toid)
end
end