diff --git a/lib/graphvix/graph.ex b/lib/graphvix/graph.ex index 472e0fe..bf24e35 100644 --- a/lib/graphvix/graph.ex +++ b/lib/graphvix/graph.ex @@ -1,8 +1,16 @@ defmodule Graphvix.Graph do + defstruct [ + digraph: nil, + global_properties: [node: [], edge: []] + ] + def new do - :digraph.new() + %__MODULE__{ + digraph: :digraph.new() + } end + def digraph_tables(%__MODULE__{digraph: graph}), do: digraph_tables(graph) def digraph_tables({:digraph, vtab, etab, ntab, _}) do [vtab, etab, ntab] end @@ -11,19 +19,20 @@ defmodule Graphvix.Graph do next_id = get_and_increment_vertex_id(graph) attributes = Keyword.put(attributes, :label, label) vertex_id = [:"$v" | next_id] - vid = :digraph.add_vertex(graph, vertex_id, attributes) + vid = :digraph.add_vertex(graph.digraph, vertex_id, attributes) {graph, vid} end def add_edge(graph, out_from, in_to, attributes \\ []) do - eid = :digraph.add_edge(graph, out_from, in_to, attributes) + eid = :digraph.add_edge(graph.digraph, out_from, in_to, attributes) {graph, eid} end def to_dot(graph) do [ "digraph G {", - nodes_to_dot(graph), + global_properties_to_dot(graph), + vertices_to_dot(graph), edges_to_dot(graph), "}" ] |> Enum.reject(&is_nil/1) |> Enum.join("\n\n") @@ -42,6 +51,40 @@ defmodule Graphvix.Graph do {_, 0} = System.cmd("open", [filename <> ".png"]) end + def set_properties(graph, attr_for, attrs \\ []) do + Enum.reduce(attrs, graph, fn {k, v}, g -> + set_property(g, attr_for, [{k, v}]) + end) + end + + def set_property(graph, attr_for, [{key, value}]) do + properties = Keyword.get(graph.global_properties, attr_for) + new_props = Keyword.put(properties, key, value) + new_properties = Keyword.put(graph.global_properties, attr_for, new_props) + %{ graph | global_properties: new_properties } + end + + defp global_properties_to_dot(graph) do + global_props = [ + _global_properties_to_dot(graph, :node), + _global_properties_to_dot(graph, :edge) + ] |> Enum.reject(&is_nil/1) + + case length(global_props) do + 0 -> nil + _ -> Enum.join(global_props, "\n") + end + end + + defp _global_properties_to_dot(%{global_properties: global_props}, key) do + with props <- Keyword.get(global_props, key) do + case length(props) do + 0 -> nil + _ -> " #{key} #{attributes_to_dot(props)}" + end + end + end + defp elements_to_dot(table, formatting_func) do case :ets.tab2list(table) do [] -> nil @@ -56,7 +99,7 @@ defmodule Graphvix.Graph do end end - defp nodes_to_dot(graph) do + defp vertices_to_dot(graph) do [vtab, _, _] = digraph_tables(graph) elements_to_dot(vtab, fn {[_ | id], attributes} -> " v#{id} #{attributes_to_dot(attributes)}" diff --git a/test/graphvix/graph_test.exs b/test/graphvix/graph_test.exs index 0ee3420..e635f7d 100644 --- a/test/graphvix/graph_test.exs +++ b/test/graphvix/graph_test.exs @@ -4,31 +4,6 @@ defmodule Graphvix.GraphTest do alias Graphvix.Graph - # property "passing an atom generates a named graph" do - # check all name <- atom(:alphanumeric) do - # graph = Graph.new(name) - # assert graph.name == to_string(name) - # end - # end - - # property "passing a string generates a node with a label" do - # check all name <- string(:ascii, min_length: 1) do - # graph = Graph.new(name) - # assert graph.name == name - # end - # end - - # property "generating an empty dot graph" do - # check all name <- string(:ascii, min_length: 3) do - # graph = Graph.new(name) - # assert Graph.to_dot(graph) == """ - # digraph "#{name}" { - - # } - # """ |> String.trim - # end - # end - property "generating a graph with a vertex" do check all label <- string(:ascii, min_length: 3) do @@ -44,6 +19,26 @@ defmodule Graphvix.GraphTest do end end + property "generating graphs with global properties" do + check all color <- string(:ascii, min_length: 3), + color2 <- string(:ascii, min_length: 3), + e_label <- string(:printable, min_length: 5) + do + graph = Graph.new() + graph = Graph.set_property(graph, :node, color: color) + graph = Graph.set_properties(graph, :edge, color: color2, label: e_label) + + assert Graph.to_dot(graph) == """ + digraph G { + + node [color="#{color}"] + edge [label="#{e_label}",color="#{color2}"] + + } + """ |> String.trim + end + end + property "adding an edge" do check all label1 <- string(:ascii, min_length: 3), label2 <- string(:ascii, min_length: 3) @@ -52,7 +47,7 @@ defmodule Graphvix.GraphTest do {graph, v1} = Graph.add_vertex(graph, label1) {graph, v2} = Graph.add_vertex(graph, label2) {graph, _e1} = Graph.add_edge(graph, v1, v2) - {_, _, etab, _, _} = graph + {_, _, etab, _, _} = graph.digraph assert length(:ets.tab2list(etab)) == 1 end end