defmodule Archeometer.Graphs.Graphviz do
@moduledoc """
Functions for working with Graphviz graphs
"""
@dot_template """
digraph G {
bgcolor="transparent";
node [color= "#9470db" fillcolor="#ECECFF" shape="rectangle" style="filled,rounded" fontname="Arial Sans"];
<%= for module <- Map.keys(@xrefs) do %>
"<%= module %>";
<% end %>
<%= for {mod, refs} <- @xrefs do %>
<%= for ref <- refs do %>
"<%= mod %>" -> "<%= ref %>";
<% end %>
<% end %>
}
"""
@doc """
Renders an graph represented by an adjacency list into .dot format.
Returns a string containing de rendered graph.
## Examples
iex(2)> graph = %{
...(2)> :a => [:b, :c],
...(2)> :b => [:c, :d],
...(2)> :d => [:a]
...(2)> }
%{a: [:b, :c], b: [:c, :d], d: [:a]}
iex(3)> dot_str = Graphviz.render_dot(graph)
iex(3)> assert is_binary(dot_str)
"""
def render_dot(xrefs) do
@dot_template
|> EEx.eval_string(assigns: [xrefs: xrefs])
|> String.replace(~r/(\n\s+\n)+|\n{2,}/, "\n")
end
@doc """
Renders an graph represented by an adjacency list into .png format.
Returns a binary containing the rendered graph.
"""
def render_image(xrefs, format \\ "png") do
xrefs
|> render_dot()
|> write_temp_file()
|> gen_from_dot(format)
end
defp write_temp_file(dot_str) do
{:ok, fd, file_path} = Mix.Project.config()[:app] |> to_string() |> Temp.open()
IO.write(fd, dot_str)
File.close(fd)
file_path
end
defp gen_from_dot(dot_path, format) do
png_path = Temp.path!(suffix: ".#{format}")
System.cmd("dot", ["-T#{format}", dot_path, "-o", png_path])
File.read!(png_path)
end
end