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

Visual themes for `Choreo` architecture diagrams.

A theme controls node shapes, colours, fonts, edge styling, graph layout,
and cluster defaults. Use `custom/1` to build your own, or use the built-in
atom shortcuts (`:default`, `:dark`, `:minimal`).

## Built-in themes

    # Warm, top-down layout with type-coloured nodes (default)
    Choreo.to_dot(system, theme: :default)

    # Dark background with neon accents
    Choreo.to_dot(system, theme: :dark)

    # Wireframe look — monochrome, no fills
    Choreo.to_dot(system, theme: :minimal)

## Custom themes

    theme =
      Choreo.Theme.custom(
        name: "brand",
        colors: [database: "#1e3a8a", service: "#047857"],
        graph_bgcolor: "#f8fafc",
        graph_rankdir: :lr
      )

    Choreo.to_dot(system, theme: theme)

## Per-type overrides

The `shapes` and `colors` maps accept any node `:node_type` as a key. Missing
types fall back to the built-in defaults (`:box` shape, `"#9ca3af"` colour).

## Cluster theming

You can set default cluster styling so every `add_cluster/3` inherits a
look unless it provides its own `:fillcolor`, `:style`, or `:color`.

    Choreo.Theme.custom(
      cluster_fillcolor: "#e2e8f0",
      cluster_style: :rounded,
      cluster_color: "#64748b"
    )

# `t`

```elixir
@type t() :: %Choreo.Theme{
  cluster_color: String.t() | nil,
  cluster_fillcolor: String.t() | nil,
  cluster_style: Yog.Render.DOT.style() | nil,
  colors: %{required(atom()) =&gt; String.t()},
  edge_color: String.t(),
  edge_fontname: String.t(),
  edge_fontsize: integer(),
  edge_penwidth: float(),
  graph_bgcolor: String.t() | nil,
  graph_nodesep: float(),
  graph_rankdir: Yog.Render.DOT.rank_dir() | nil,
  graph_ranksep: float(),
  graph_splines: Yog.Render.DOT.splines() | nil,
  name: atom() | String.t(),
  node_fontcolor: String.t(),
  node_fontname: String.t(),
  node_fontsize: integer(),
  shapes: %{required(atom()) =&gt; Yog.Render.DOT.node_shape()}
}
```

# `color`

```elixir
@spec color(t(), atom()) :: String.t()
```

Resolves the effective colour for a node type, falling back to defaults.

## Examples

    iex> theme = Choreo.Theme.custom(colors: %{database: "#ff0000"})
    iex> Choreo.Theme.color(theme, :database)
    "#ff0000"
    iex> Choreo.Theme.color(theme, :service)
    "#10b981"

# `color_from_scale`

```elixir
@spec color_from_scale(float(), atom() | [String.t()]) :: String.t()
```

Returns a hex color from a predefined scale based on a normalized value (0.0 to 1.0).

## Palettes
- `:heat` — Yellow to Red (9 steps)
- `:cool` — Light Blue to Dark Blue (9 steps)
- `:spectral` — Blue-Yellow-Red (5 steps)

# `custom`

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

Builds a custom theme from keyword options.

Only the fields you specify are overridden; everything else falls back to
the default theme values.

## Examples

    iex> theme = Choreo.Theme.custom(name: :brand, graph_rankdir: :lr)
    iex> theme.name
    :brand
    iex> theme.graph_rankdir
    :lr

# `dark`

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

Dark theme — dark background, neon accents.

## Examples

    iex> theme = Choreo.Theme.dark()
    iex> theme.name
    :dark
    iex> theme.graph_bgcolor
    "#0f172a"

# `dark?`

```elixir
@spec dark?(t()) :: boolean()
```

Returns `true` if the theme is considered a dark-background theme.

Used by renderers that need to flip cluster fill colours between
light and dark variants.

## Examples

    iex> Choreo.Theme.dark?(Choreo.Theme.dark())
    true
    iex> Choreo.Theme.dark?(Choreo.Theme.default())
    false

# `default`

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

The default theme — type-coloured nodes, top-down layout.

## Examples

    iex> theme = Choreo.Theme.default()
    iex> theme.name
    :default
    iex> theme.graph_rankdir
    nil

# `default_colors`

```elixir
@spec default_colors() :: %{required(atom()) =&gt; String.t()}
```

Returns the default colour palette.

## Examples

    iex> colors = Choreo.Theme.default_colors()
    iex> colors[:database]
    "#3b82f6"
    iex> colors[:service]
    "#10b981"

# `default_shapes`

```elixir
@spec default_shapes() :: %{required(atom()) =&gt; Yog.Render.DOT.node_shape()}
```

Returns the default shape palette.

## Examples

    iex> shapes = Choreo.Theme.default_shapes()
    iex> shapes[:database]
    :cylinder
    iex> shapes[:service]
    :box3d

# `forest`

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

Forest theme — lush green palette, earth tones.

# `graph_overrides`

```elixir
@spec graph_overrides(t()) :: map()
```

Extracts graph-level DOT attribute overrides from a theme,
dropping any nil values.

# `minimal`

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

Minimal theme — monochrome wireframe, thin edges, no fills.

## Examples

    iex> theme = Choreo.Theme.minimal()
    iex> theme.name
    :minimal
    iex> theme.colors[:database]
    "#ffffff"
    iex> theme.shapes[:service]
    :box

# `ocean`

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

Ocean theme — deep blue palette, cool background.

# `override`

```elixir
@spec override(t(), keyword()) :: t()
```

Overrides specific fields in a theme, including deep-merging colors and shapes.

## Examples

    iex> theme = Choreo.Theme.default() |> Choreo.Theme.override(graph_rankdir: :lr)
    iex> theme.graph_rankdir
    :lr

# `resolve`

```elixir
@spec resolve(t() | atom()) :: t()
```

Resolves a theme atom shortcut to a `%Theme{}` struct.

Pass-through if already a struct. Falls back to `default/0` for unknown atoms.
Does not dispatch to diagram-specific defaults — use this only from renderers
that use the standard Choreo themes (e.g. `Choreo.Infrastructure`).

## Examples

    iex> Choreo.Theme.resolve(:dark) == Choreo.Theme.dark()
    true
    iex> Choreo.Theme.resolve(:unknown) == Choreo.Theme.default()
    true
    iex> theme = Choreo.Theme.default()
    iex> Choreo.Theme.resolve(theme) == theme
    true

# `shape`

```elixir
@spec shape(t(), atom()) :: Yog.Render.DOT.node_shape()
```

Resolves the effective shape for a node type, falling back to defaults.

## Examples

    iex> theme = Choreo.Theme.custom(shapes: %{database: :box})
    iex> Choreo.Theme.shape(theme, :database)
    :box
    iex> Choreo.Theme.shape(theme, :service)
    :box3d

# `warm`

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

Warm theme — sunset palette, warm background.

---

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