# `Yog.Render.DOT`
[🔗](https://github.com/code-shoily/yog_ex/blob/v0.97.0/lib/yog/render/dot.ex#L1)

DOT (Graphviz) format export for visualizing graphs.

This module exports graphs to the [DOT language](https://graphviz.org/doc/info/lang.html),
which is the native format for [Graphviz](https://graphviz.org/) - a powerful open-source
graph visualization tool. The exported files can be rendered to PNG, SVG, PDF, and other
formats using the `dot`, `neato`, `circo`, or other Graphviz layout engines.

## Quick Start

    # Export with default styling
    dot_string = Yog.Render.DOT.to_dot(my_graph, Yog.Render.DOT.default_options())

    # Write to file and render with Graphviz CLI
    # $ dot -Tpng output.dot -o graph.png

## Customization

Use `t:options/0` to customize:
- Node labels and shapes
- Edge labels and styles
- **Per-node and per-edge attributes** (custom colors, shapes, etc.)
- **Subgraphs/clusters** for visual grouping
- Highlight specific nodes or paths
- Graph direction (LR, TB, etc.)

## Generic Data Types

The `to_dot` function works with any node and edge data types. Use
`default_options_with_edge_formatter/1` when your edge data is not a String.

## Per-Element Styling

Provide custom attribute functions for fine-grained control:

    options = %{
      Yog.Render.DOT.default_options() |
      node_attributes: fn id, data ->
        case id do
          1 -> [{:fillcolor, "green"}, {:shape, "diamond"}]
          _ -> []
        end
      end,
      edge_attributes: fn from, to, weight ->
        if weight > 10 do
          [{:color, "red"}, {:penwidth, 2}]
        else
          []
        end
      end
    }

## Subgraphs and Clusters

Group nodes visually using subgraphs:

    options = %{
      Yog.Render.DOT.default_options() |
      subgraphs: [
        %{
          name: "cluster_0",
          label: "Cluster A",
          node_ids: [1, 2, 3],
          style: :filled,
          fillcolor: "lightgrey",
          color: nil
        }
      ]
    }

## Rendering Options

| Engine | Best For |
|--------|----------|
| `dot` | Hierarchical layouts (DAGs, trees) |
| `neato` | Spring-based layouts (undirected) |
| `circo` | Circular layouts |
| `fdp` | Force-directed layouts |
| `sfdp` | Large graphs |

## References

- [Graphviz Documentation](https://graphviz.org/documentation/)
- [DOT Language Guide](https://graphviz.org/doc/info/lang.html)
- [Node Shapes](https://graphviz.org/doc/info/shapes.html)
- [Arrow Styles](https://graphviz.org/doc/info/arrows.html)
- [Cluster/Subgraph Syntax](https://graphviz.org/docs/attrs/cluster/)

# `arrow_style`

```elixir
@type arrow_style() ::
  :normal
  | :dot
  | :diamond
  | :odiamond
  | :box
  | :crow
  | :vee
  | :inv
  | :tee
  | :none
  | {:custom, String.t()}
```

Arrow head/tail style

# `layout`

```elixir
@type layout() ::
  :dot
  | :neato
  | :circo
  | :fdp
  | :sfdp
  | :twopi
  | :osage
  | {:custom, String.t()}
```

Graphviz layout engine

# `node_shape`

```elixir
@type node_shape() ::
  :box
  | :box3d
  | :circle
  | :cloud
  | :component
  | :cylinder
  | :diamond
  | :doublecircle
  | :ellipse
  | :folder
  | :hexagon
  | :house
  | :invhouse
  | :invtriangle
  | :note
  | :octagon
  | :parallelogram
  | :pentagon
  | :plain
  | :plaintext
  | :point
  | :rect
  | :rectangle
  | :square
  | :tab
  | :trapezoid
  | :triangle
  | :underline
  | {:custom, String.t()}
```

Node shapes

# `options`

```elixir
@type options() :: %{
  node_label: (Yog.node_id(), any() -&gt; String.t()),
  edge_label: (any() -&gt; String.t()),
  highlighted_nodes: [Yog.node_id()] | nil,
  highlighted_edges: [{Yog.node_id(), Yog.node_id()}] | nil,
  node_attributes: (Yog.node_id(), any() -&gt; [{atom(), String.t()}]),
  edge_attributes: (Yog.node_id(), Yog.node_id(), any() -&gt;
                      [{atom(), String.t()}]),
  subgraphs: [subgraph()] | nil,
  ranks: [{:same | :min | :max | :source | :sink, [Yog.node_id()]}] | nil,
  graph_name: String.t(),
  layout: layout() | nil,
  rankdir: rank_dir() | nil,
  bgcolor: String.t() | nil,
  splines: splines() | nil,
  overlap: overlap() | nil,
  nodesep: float() | nil,
  ranksep: float() | nil,
  node_shape: node_shape(),
  node_color: String.t(),
  node_style: style(),
  node_fontname: String.t(),
  node_fontsize: integer(),
  node_fontcolor: String.t(),
  edge_color: String.t(),
  edge_style: style(),
  edge_fontname: String.t(),
  edge_fontsize: integer(),
  edge_penwidth: float(),
  arrowhead: arrow_style() | nil,
  arrowtail: arrow_style() | nil,
  highlight_color: String.t(),
  highlight_penwidth: float()
}
```

Options for customizing DOT (Graphviz) diagram rendering

# `overlap`

```elixir
@type overlap() :: true | false | :scale | :scalexy | :prism | {:custom, String.t()}
```

Overlap handling

# `rank_dir`

```elixir
@type rank_dir() :: :tb | :lr | :bt | :rl
```

Graph direction (rank direction)

# `splines`

```elixir
@type splines() :: :line | :polyline | :curved | :ortho | :spline | :none
```

Edge routing style

# `style`

```elixir
@type style() ::
  :solid
  | :dashed
  | :dotted
  | :bold
  | :filled
  | :rounded
  | :diagonals
  | :striped
  | :wedged
```

Visual style

# `subgraph`

```elixir
@type subgraph() :: %{
  name: String.t(),
  label: String.t() | nil,
  node_ids: [Yog.node_id()] | nil,
  style: style() | nil,
  fillcolor: String.t() | nil,
  color: String.t() | nil,
  subgraphs: [subgraph()] | nil
}
```

A subgraph (cluster) for grouping nodes visually in the diagram.

In Graphviz, subgraphs with names starting with "cluster_" are rendered
as bounded rectangles around the contained nodes.

Subgraphs may be nested by providing the `subgraphs` key, which creates
a cluster hierarchy (e.g. VPC → AZ → nodes).

# `community_to_options`

```elixir
@spec community_to_options(Yog.Community.Result.t(), options()) :: options()
```

Creates DOT options that color nodes by community assignment.

Each community gets a distinct color from a generated palette. The palette
cycles through visually distinct hues.

## Example

    result = Yog.Community.Louvain.detect(graph)
    options = Yog.Render.DOT.community_to_options(result)
    dot_string = Yog.Render.DOT.to_dot(graph, options)

# `cut_to_options`

```elixir
@spec cut_to_options(Yog.Flow.MinCutResult.t(), options()) :: options()
```

Creates DOT options that color the source and sink sides of a min-cut.

Source-side nodes are colored with `source_color` (default: light blue),
sink-side nodes with `sink_color` (default: light coral).

Requires the `MinCutResult` to have `source_side` and `sink_side` populated
(use `track_partitions: true` or `extract_min_cut/1`).

## Example

    result = Yog.Flow.MinCut.global_min_cut(graph, track_partitions: true)
    options = Yog.Render.DOT.cut_to_options(result)
    dot_string = Yog.Render.DOT.to_dot(graph, options)

# `default_options`

```elixir
@spec default_options() :: options()
```

Creates default DOT options with simple labeling and sensible styling.

Default configuration:
- Layout: Auto-detected by Graphviz
- Direction: Top-to-bottom
- Node shape: Ellipse
- Colors: Light blue nodes, black edges
- Font: Helvetica 12pt

## Examples

    iex> opts = Yog.Render.DOT.default_options()
    iex> opts.graph_name
    "G"
    iex> opts.node_shape
    :ellipse
    iex> opts.node_color
    "lightblue"

# `default_options_with`

```elixir
@spec default_options_with(
  node_label: (Yog.node_id(), any() -&gt; String.t()),
  edge_label: (any() -&gt; String.t())
) :: options()
```

Creates default DOT options with custom label formatters for both nodes and edges.

## Example

    options = Yog.Render.DOT.default_options_with(
      node_label: fn id, data -> "#{data} (#{id})" end,
      edge_label: fn weight -> "#{weight} ms" end
    )

# `default_options_with_edge_formatter`

```elixir
@spec default_options_with_edge_formatter((any() -&gt; String.t())) :: options()
```

Creates default DOT options with a custom edge formatter.

Use this when your graph has non-String edge data (e.g., Int, Float, custom types).

## Example

    # For a graph with Int edge weights
    options = Yog.Render.DOT.default_options_with_edge_formatter(fn weight ->
      Integer.to_string(weight)
    end)

# `matching_to_options`

```elixir
@spec matching_to_options(%{required(Yog.node_id()) =&gt; Yog.node_id()}, options()) ::
  options()
```

Creates DOT options that highlight matched edges from a matching result.

Works with results from both `Yog.Matching.hopcroft_karp/1` and
`Yog.Matching.hungarian/2` (the matching map component).

## Example

    matching = Yog.Matching.hopcroft_karp(graph)
    options = Yog.Render.DOT.matching_to_options(matching)
    dot_string = Yog.Render.DOT.to_dot(graph, options)

# `mst_to_options`

```elixir
@spec mst_to_options(Yog.MST.Result.t(), options()) :: options()
```

Creates DOT options that highlight an MST result.

MST edges are highlighted and non-MST edges use default styling.

## Example

    result = Yog.MST.kruskal(graph)
    options = Yog.Render.DOT.mst_to_options(result)
    dot_string = Yog.Render.DOT.to_dot(graph, options)

# `path_to_options`

```elixir
@spec path_to_options(map(), options()) :: options()
```

Converts a shortest path result to highlighted DOT options.

Creates a copy of the base options with the path's nodes and edges
set to be highlighted. This is useful for visualizing algorithm results.

## Example

    case Yog.Pathfinding.Dijkstra.shortest_path(...) do
      {:ok, path} ->
        options = Yog.Render.DOT.path_to_options(path, Yog.Render.DOT.default_options())
        dot_string = Yog.Render.DOT.to_dot(graph, options)
      :error ->
        ""
    end

# `theme`

```elixir
@spec theme(atom()) :: options()
```

Returns a pre-configured theme as DOT options.

Available themes:
- `:default` — Light blue nodes, black edges (same as `default_options/0`)
- `:dark` — Dark background with neon accent colors, ideal for dark UIs
- `:minimal` — Clean wireframe look with no fills and thin lines
- `:presentation` — Large fonts and bold colors for slides and demos

## Examples

    iex> opts = Yog.Render.DOT.theme(:dark)
    iex> opts.bgcolor
    "#1a1a2e"
    iex> opts.node_color
    "#16213e"

    iex> opts = Yog.Render.DOT.theme(:minimal)
    iex> opts.node_style
    :solid

    iex> opts = Yog.Render.DOT.theme(:presentation)
    iex> opts.node_fontsize
    18

# `to_dot`

```elixir
@spec to_dot(Yog.graph(), options()) :: String.t()
```

Converts a graph to DOT (Graphviz) syntax.

Works with any node data type and edge data type. Use the options
to customize labels, styling, and to define subgraphs.

**Time Complexity:** O(V + E + S) where S is the total number of nodes
across all subgraphs.

## Example

    graph =
      Yog.directed()
      |> Yog.add_node(1, "Start")
      |> Yog.add_node(2, "Process")
      |> Yog.add_edge_ensure(from: 1, to: 2, with: "5")

    diagram = Yog.Render.DOT.to_dot(graph, Yog.Render.DOT.default_options())

---

*Consult [api-reference.md](api-reference.md) for complete listing*
