# `Choreo.C4`
[🔗](https://github.com/code-shoily/choreo/blob/v0.14.1/lib/choreo/c4.ex#L1)

C4 Model architecture diagram builder on top of Yog.

`Choreo.C4` lets you document software architecture using the
[C4 model](https://c4model.com/) — a simple hierarchical approach
for visualising software architecture at different levels of abstraction.

## The four levels

  * **L1 System Context** — people and software systems
  * **L2 Containers** — applications and data stores inside a system
  * **L3 Components** — building blocks inside a container
  * **L4 Code** — classes / interfaces (delegate to `Choreo.UML`)

## When to use

Use `Choreo.C4` when you need to communicate architecture to both
technical and non-technical stakeholders. The zoom levels map directly
to the C4 abstraction levels, so you can generate Context, Container,
and Component diagrams from a single model.

## Quick Start

    c4 =
      Choreo.C4.new()
      |> Choreo.C4.add_person(:customer, label: "Customer",
           description: "A user of the bank")
      |> Choreo.C4.add_software_system(:banking, label: "Internet Banking",
           description: "Allows customers to check balances", scope: :in)
      |> Choreo.C4.add_software_system(:mainframe, label: "Mainframe",
           description: "Stores core banking info", scope: :out)
      |> Choreo.C4.add_relationship(:customer, :banking,
           label: "Views balances using")
      |> Choreo.C4.add_relationship(:banking, :mainframe,
           label: "Gets account info from")
      # L2 — Containers inside the in-scope system
      |> Choreo.C4.add_container(:web_app, label: "Web App",
           technology: "JavaScript, React", parent: :banking)
      |> Choreo.C4.add_container(:api, label: "API",
           technology: "Elixir, Phoenix", parent: :banking)
      |> Choreo.C4.add_container(:db, label: "Database",
           technology: "Postgres", parent: :banking)
      |> Choreo.C4.add_relationship(:customer, :web_app, label: "Uses")
      |> Choreo.C4.add_relationship(:web_app, :api, label: "Makes API calls to")
      |> Choreo.C4.add_relationship(:api, :db, label: "Reads from and writes to")
      |> Choreo.C4.add_relationship(:api, :mainframe, label: "Makes RPC calls to")
      # L3 — Components inside a container
      |> Choreo.C4.add_component(:signin, label: "Sign In Controller",
           technology: "Phoenix Controller", parent: :api)
      |> Choreo.C4.add_component(:accounts, label: "Accounts Context",
           technology: "Domain logic", parent: :api)
      |> Choreo.C4.add_relationship(:signin, :accounts, label: "Uses")

    # Generate diagrams at different zoom levels
    context   = Choreo.View.zoom(c4, level: 0)
    container = Choreo.View.zoom(c4, level: 1)
    component = Choreo.View.zoom(c4, level: 2)

    dot = Choreo.C4.to_dot(c4)
    mermaid = Choreo.C4.to_mermaid(c4)

## Analysis

    # Find containers with no relationships
    Choreo.C4.Analysis.orphan_nodes(c4)

    # Validate structural soundness
    Choreo.C4.Analysis.validate(c4)

    # Find missing descriptions
    Choreo.C4.Analysis.missing_descriptions(c4)

    # Find missing technology labels
    Choreo.C4.Analysis.missing_technology(c4)

# `t`

```elixir
@type t() :: %Choreo.C4{
  clusters: %{required(String.t()) =&gt; map()},
  edge_meta: %{optional(Yog.Multi.Graph.edge_id()) =&gt; map()},
  graph: Yog.Multi.Graph.t(),
  scope: Yog.node_id() | nil,
  strict: boolean()
}
```

# `add_cluster`

```elixir
@spec add_cluster(t(), String.t() | atom(), keyword()) :: t()
```

Defines a cluster (subgraph) for grouping nodes visually.

In C4 diagrams clusters are often used to group containers by
their parent software system or components by their parent container.

## Options

* `:parent` (`t:String.t/0`) - Parent cluster name for nesting.

* `:label` (`t:String.t/0`) - Display label for the cluster.

* `:style` (`t:String.t/0`) - Visual style.

* `:fillcolor` (`t:String.t/0`) - Background color override.

* `:color` (`t:String.t/0`) - Border color override.

## Examples

    iex> c4 = Choreo.C4.new() |> Choreo.C4.add_cluster("banking", label: "Internet Banking")
    iex> c4.clusters["cluster_banking"].label
    "Internet Banking"

# `add_component`

```elixir
@spec add_component(t(), Yog.node_id(), keyword()) :: t()
```

Adds a component (building block inside a container).

## Options

* `:parent` - Parent container node ID.

* `:label` (`t:String.t/0`) - Display label for the node.

* `:description` (`t:String.t/0`) - Longer description shown as tooltip.

* `:technology` (`t:String.t/0`) - Technology stack (e.g. 'Elixir, Phoenix').

* `:shape` (`t:atom/0`) - Shape override for DOT output.

* `:fillcolor` (`t:String.t/0`) - Background color override.

* `:fontcolor` (`t:String.t/0`) - Font color override.

* `:style` (`t:String.t/0`) - Style override.

* `:penwidth` - Border thickness override.

* `:image` (`t:String.t/0`) - Image/icon path or URL override.

## Examples

    iex> c4 = Choreo.C4.new()
    ...>   |> Choreo.C4.add_software_system(:banking)
    ...>   |> Choreo.C4.add_container(:api, parent: :banking)
    ...>   |> Choreo.C4.add_component(:auth, label: "Auth Controller", technology: "Phoenix", parent: :api)
    iex> :auth in Choreo.C4.nodes(c4)
    true
    iex> Map.get(c4.graph.nodes, :auth).node_type
    :component
    iex> Map.get(c4.graph.nodes, :auth).parent
    :api

# `add_container`

```elixir
@spec add_container(t(), Yog.node_id(), keyword()) :: t()
```

Adds a container (application or data store within a software system).

## Options

* `:parent` - Parent software system node ID.

* `:label` (`t:String.t/0`) - Display label for the node.

* `:description` (`t:String.t/0`) - Longer description shown as tooltip.

* `:technology` (`t:String.t/0`) - Technology stack (e.g. 'Elixir, Phoenix').

* `:shape` (`t:atom/0`) - Shape override for DOT output.

* `:fillcolor` (`t:String.t/0`) - Background color override.

* `:fontcolor` (`t:String.t/0`) - Font color override.

* `:style` (`t:String.t/0`) - Style override.

* `:penwidth` - Border thickness override.

* `:image` (`t:String.t/0`) - Image/icon path or URL override.

## Examples

    iex> c4 = Choreo.C4.new()
    ...>   |> Choreo.C4.add_software_system(:banking)
    ...>   |> Choreo.C4.add_container(:api, label: "API", technology: "Phoenix", parent: :banking)
    iex> :api in Choreo.C4.nodes(c4)
    true
    iex> Map.get(c4.graph.nodes, :api).node_type
    :container
    iex> Map.get(c4.graph.nodes, :api).parent
    :banking

# `add_person`

```elixir
@spec add_person(t(), Yog.node_id(), keyword()) :: t()
```

Adds a person (user, actor, or role).

## Options

* `:label` (`t:String.t/0`) - Display label for the node.

* `:description` (`t:String.t/0`) - Longer description shown as tooltip.

* `:technology` (`t:String.t/0`) - Technology stack (e.g. 'Elixir, Phoenix').

* `:shape` (`t:atom/0`) - Shape override for DOT output.

* `:fillcolor` (`t:String.t/0`) - Background color override.

* `:fontcolor` (`t:String.t/0`) - Font color override.

* `:style` (`t:String.t/0`) - Style override.

* `:penwidth` - Border thickness override.

* `:image` (`t:String.t/0`) - Image/icon path or URL override.

## Examples

    iex> c4 = Choreo.C4.new() |> Choreo.C4.add_person(:customer, label: "Customer")
    iex> :customer in Choreo.C4.nodes(c4)
    true
    iex> Map.get(c4.graph.nodes, :customer).node_type
    :person

# `add_relationship`

```elixir
@spec add_relationship(t(), Yog.node_id(), Yog.node_id(), keyword()) :: t()
```

Adds a relationship (directed edge) between two C4 elements.

## Options

* `:label` (`t:String.t/0`) - Description of the relationship.

* `:technology` (`t:String.t/0`) - Technology used for the relationship (e.g. 'HTTPS', 'gRPC').

## Examples

    iex> c4 = Choreo.C4.new()
    ...>   |> Choreo.C4.add_person(:customer)
    ...>   |> Choreo.C4.add_software_system(:banking)
    ...>   |> Choreo.C4.add_relationship(:customer, :banking, label: "Uses")
    iex> Choreo.C4.edges(c4)
    [{:customer, :banking, 1}]

# `add_software_system`

```elixir
@spec add_software_system(t(), Yog.node_id(), keyword()) :: t()
```

Adds a software system.

## Options

* `:scope` - Whether this system is in scope (:in) or external (:out) of the diagram being produced. The default value is `:out`.

* `:label` (`t:String.t/0`) - Display label for the node.

* `:description` (`t:String.t/0`) - Longer description shown as tooltip.

* `:technology` (`t:String.t/0`) - Technology stack (e.g. 'Elixir, Phoenix').

* `:shape` (`t:atom/0`) - Shape override for DOT output.

* `:fillcolor` (`t:String.t/0`) - Background color override.

* `:fontcolor` (`t:String.t/0`) - Font color override.

* `:style` (`t:String.t/0`) - Style override.

* `:penwidth` - Border thickness override.

* `:image` (`t:String.t/0`) - Image/icon path or URL override.

## Examples

    iex> c4 = Choreo.C4.new() |> Choreo.C4.add_software_system(:banking, label: "Banking", scope: :in)
    iex> :banking in Choreo.C4.nodes(c4)
    true
    iex> Map.get(c4.graph.nodes, :banking).node_type
    :software_system
    iex> Map.get(c4.graph.nodes, :banking).scope
    :in

# `children`

```elixir
@spec children(t(), Yog.node_id()) :: [Yog.node_id()]
```

Returns all children of a given parent node.

## Examples

    iex> c4 = Choreo.C4.new()
    ...>   |> Choreo.C4.add_software_system(:banking)
    ...>   |> Choreo.C4.add_container(:api, parent: :banking)
    ...>   |> Choreo.C4.add_container(:web, parent: :banking)
    iex> Enum.sort(Choreo.C4.children(c4, :banking))
    [:api, :web]

# `clear_scope`

```elixir
@spec clear_scope(t()) :: t()
```

Clears the explicit scope and removes all `scope: :in` tags.

## Examples

    iex> c4 = Choreo.C4.new() |> Choreo.C4.add_software_system(:banking)
    iex> c4 = Choreo.C4.set_scope(c4, :banking)
    iex> c4 = Choreo.C4.clear_scope(c4)
    iex> c4.scope
    nil

    iex> c4 = Choreo.C4.new() |> Choreo.C4.add_software_system(:banking, scope: :in)
    iex> c4 = Choreo.C4.clear_scope(c4)
    iex> c4.scope
    nil
    iex> Map.get(c4.graph.nodes, :banking).scope
    :out

# `edges`

```elixir
@spec edges(t()) :: [{Yog.node_id(), Yog.node_id(), number()}]
```

Returns all edges as `{from, to, weight}` tuples.

## Examples

    iex> c4 = Choreo.C4.new()
    ...>   |> Choreo.C4.add_person(:a)
    ...>   |> Choreo.C4.add_software_system(:b)
    ...>   |> Choreo.C4.add_relationship(:a, :b)
    iex> Choreo.C4.edges(c4)
    [{:a, :b, 1}]

# `edges_with_meta`

```elixir
@spec edges_with_meta(t()) :: [{Yog.node_id(), Yog.node_id(), number(), map()}]
```

Returns all edges with their metadata as `{from, to, weight, meta}` tuples.

## Examples

    iex> c4 = Choreo.C4.new()
    ...>   |> Choreo.C4.add_person(:a)
    ...>   |> Choreo.C4.add_software_system(:b)
    ...>   |> Choreo.C4.add_relationship(:a, :b, label: "Uses")
    iex> [{_, _, _, meta}] = Choreo.C4.edges_with_meta(c4)
    iex> meta.label
    "Uses"

# `new`

```elixir
@spec new(keyword()) :: t()
```

Creates a new empty C4 model.

## Examples

    iex> c4 = Choreo.C4.new()
    iex> Choreo.C4.nodes(c4)
    []
    iex> Choreo.C4.edges(c4)
    []
    iex> c4.scope
    nil

# `nodes`

```elixir
@spec nodes(t()) :: [Yog.node_id()]
```

Returns all node IDs in the C4 model.

## Examples

    iex> c4 = Choreo.C4.new() |> Choreo.C4.add_person(:a) |> Choreo.C4.add_software_system(:b)
    iex> Enum.sort(Choreo.C4.nodes(c4))
    [:a, :b]

# `nodes_of_type`

```elixir
@spec nodes_of_type(t(), atom()) :: [Yog.node_id()]
```

Returns all nodes of a given C4 type.

## Examples

    iex> c4 = Choreo.C4.new()
    ...>   |> Choreo.C4.add_person(:a)
    ...>   |> Choreo.C4.add_person(:b)
    ...>   |> Choreo.C4.add_software_system(:c)
    iex> Enum.sort(Choreo.C4.nodes_of_type(c4, :person))
    [:a, :b]
    iex> Choreo.C4.nodes_of_type(c4, :software_system)
    [:c]

# `parent`

```elixir
@spec parent(t(), Yog.node_id()) :: Yog.node_id() | nil
```

Returns the parent ID of a node, or `nil`.

## Examples

    iex> c4 = Choreo.C4.new()
    ...>   |> Choreo.C4.add_software_system(:banking)
    ...>   |> Choreo.C4.add_container(:api, parent: :banking)
    iex> Choreo.C4.parent(c4, :api)
    :banking
    iex> Choreo.C4.parent(c4, :banking)
    nil

# `scope`

```elixir
@spec scope(t()) :: Yog.node_id() | nil
```

Returns the scope node ID, or `nil`.

## Examples

    iex> c4 = Choreo.C4.new() |> Choreo.C4.add_software_system(:banking)
    iex> c4 = Choreo.C4.set_scope(c4, :banking)
    iex> Choreo.C4.scope(c4)
    :banking

# `set_scope`

```elixir
@spec set_scope(t(), Yog.node_id()) :: t()
```

Sets the in-scope node for drill-down diagrams.

When generating a Container diagram, the scope is typically the
software system being expanded. For a Component diagram, the scope
is the container being expanded.

## Examples

    iex> c4 = Choreo.C4.new() |> Choreo.C4.add_software_system(:banking, scope: :in)
    iex> c4 = Choreo.C4.set_scope(c4, :banking)
    iex> c4.scope
    :banking

# `theme`

```elixir
@spec theme(atom(), keyword()) :: Choreo.Theme.t()
```

Returns a theme for `Choreo.C4` diagrams.

## Examples

    iex> theme = Choreo.C4.theme(:default)
    iex> theme.name
    :c4_default

# `to_dot`

```elixir
@spec to_dot(t(), keyword()) :: String.t()
```

Renders the C4 model to DOT format.

## Options

  * `:theme` — `:default`, `:dark`, `:warm`, `:forest`, `:ocean`, or a `Choreo.Theme` struct
  * `:rankdir` — Graphviz layout direction, e.g. `:lr`, `:tb`, `:bt`, or `:rl`
  * `:highlighted_nodes` — list of node IDs to highlight
  * `:highlighted_edges` — list of edge IDs or `{from, to}` tuples to highlight

## Examples

    iex> c4 = Choreo.C4.new() |> Choreo.C4.add_person(:a)
    iex> dot = Choreo.C4.to_dot(c4)
    iex> String.contains?(dot, "digraph")
    true

# `to_graph`

```elixir
@spec to_graph(t()) :: Yog.Multi.Graph.t()
```

Returns the raw `Yog.Multi.Graph` struct underpinning the model.

## Examples

    iex> c4 = Choreo.C4.new()
    iex> graph = Choreo.C4.to_graph(c4)
    iex> graph.kind
    :directed

# `to_mermaid`

```elixir
@spec to_mermaid(t(), keyword()) :: String.t()
```

Renders the C4 model to Mermaid.js flowchart syntax.

## Options

  * `:theme` — `:default`, `:dark`, `:warm`, `:forest`, `:ocean`, or a `Choreo.Theme` struct
  * `:direction` — `:lr` (default), `:td`, `:rl`, `:bt`
  * `:highlighted_nodes` — list of node IDs to highlight
  * `:highlighted_edges` — list of edge IDs or `{from, to}` tuples to highlight

## Examples

    iex> c4 = Choreo.C4.new() |> Choreo.C4.add_person(:a)
    iex> mermaid = Choreo.C4.to_mermaid(c4)
    iex> String.contains?(mermaid, "graph LR")
    true

---

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