lib/archeometer/graphs/graphviz.ex

defmodule Archeometer.Graphs.Graphviz do
  @moduledoc """
    Functions for working with Graphviz graphs
  """

  require EEx
  alias Archeometer.Graphs.Colors

  EEx.function_from_file(
    :defp,
    :render_simple_dot,
    "priv/templates/graphviz/simple.eex",
    [:xrefs, :color]
  )

  EEx.function_from_file(
    :defp,
    :render_centrality_colored_dot,
    "priv/templates/graphviz/centrality_colored.eex",
    [:xrefs, :color, :centrality]
  )

  @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, centrality \\ %{}) do
    do_render_dot(xrefs, centrality)
    |> String.replace(~r/(\n\s+\n)+|\n{2,}/, "\n")
  end

  defp do_render_dot(xrefs, centrality) when centrality == %{} do
    render_simple_dot(xrefs, Colors)
  end

  defp do_render_dot(xrefs, centrality) do
    render_centrality_colored_dot(xrefs, Colors, centrality)
  end

  @doc """
  Renders an graph represented by an adjacency list into .png format.

  Returns a binary containing the rendered graph.

  """
  def render_image(xrefs, centrality \\ %{}, format \\ "png") do
    xrefs
    |> render_dot(centrality)
    |> 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