defmodule Graphvix.Subgraph do
@moduledoc """
[Internal] Models a subgraph or cluster for inclusion in a graph.
The functions included in this module are for internal use only. See
* `Graphvix.Graph.add_subgraph/3`
* `Graphvix.Graph.add_cluster/3`
for the public interface for creating and including subgraphs and clusters.
"""
import Graphvix.DotHelpers
defstruct id: nil,
vertex_ids: [],
global_properties: [node: [], edge: []],
subgraph_properties: [],
is_cluster: false
@doc false
def new(id, vertex_ids, is_cluster \\ false, properties \\ []) do
node_properties = Keyword.get(properties, :node, [])
edge_properties = Keyword.get(properties, :edge, [])
subgraph_properties = properties |> Keyword.delete(:node) |> Keyword.delete(:edge)
%Graphvix.Subgraph{
id: id_prefix(is_cluster) <> "#{id}",
is_cluster: is_cluster,
vertex_ids: vertex_ids,
global_properties: [
node: node_properties,
edge: edge_properties
],
subgraph_properties: subgraph_properties
}
end
@doc false
def to_dot(subgraph, graph) do
[vtab, _, _] = Graphvix.Graph.digraph_tables(graph)
vertices_from_graph = :ets.tab2list(vtab)
[
"subgraph #{subgraph.id} {",
global_properties_to_dot(subgraph),
subgraph_properties_to_dot(subgraph),
subgraph_vertices_to_dot(subgraph.vertex_ids, vertices_from_graph),
subgraph_edges_to_dot(subgraph, graph),
"}"
]
|> List.flatten()
|> compact()
|> Enum.map_join("\n\n", &indent/1)
end
@doc false
def subgraph_edges_to_dot(subgraph, graph) do
subgraph
|> edges_with_both_vertices_in_subgraph(graph)
|> sort_elements_by_id()
|> elements_to_dot(fn {_, [:"$v" | v1], [:"$v" | v2], attributes} ->
"v#{v1} -> v#{v2} #{attributes_to_dot(attributes)}" |> String.trim() |> indent
end)
end
@doc false
def both_vertices_in_subgraph?(vertex_ids, vid1, vid2) do
vid1 in vertex_ids && vid2 in vertex_ids
end
## Private
defp subgraph_vertices_to_dot(subgraph_vertex_ids, vertices_from_graph) do
subgraph_vertex_ids
|> vertices_in_this_subgraph(vertices_from_graph)
|> sort_elements_by_id()
|> elements_to_dot(fn {[_ | id], attributes} ->
[
"v#{id}",
attributes_to_dot(attributes)
]
|> compact
|> Enum.join(" ")
|> indent
end)
end
defp vertices_in_this_subgraph(subgraph_vertex_ids, vertices_from_graph) do
vertices_from_graph
|> Enum.filter(fn {vid, _attributes} -> vid in subgraph_vertex_ids end)
end
defp subgraph_properties_to_dot(%{subgraph_properties: properties}) do
properties
|> Enum.map(fn {key, value} ->
indent(attribute_to_dot(key, value))
end)
|> compact()
|> return_joined_list_or_nil()
end
defp edges_with_both_vertices_in_subgraph(%{vertex_ids: vertex_ids}, graph) do
[_, etab, _] = Graphvix.Graph.digraph_tables(graph)
edges = :ets.tab2list(etab)
Enum.filter(edges, fn {_, vid1, vid2, _} ->
both_vertices_in_subgraph?(vertex_ids, vid1, vid2)
end)
end
defp id_prefix(_is_cluster = true), do: "cluster"
defp id_prefix(_is_cluster = false), do: "subgraph"
end