# Statwise Visualization Gallery

## Section

This Livebook is a runnable tour of `Statwise.Visualization`. It covers the
current chart types, result plots, styling, faceting, and export helpers.

The visualization API has two layers:

* Build chart content with functions such as `histogram/2`, `box_plot/2`, and
  `qq_plot/2`.
* Apply presentation separately with `with_theme/2`, `with_palette/2`,
  `with_style/2`, or with the `style:` export option.

## Setup

```elixir
Mix.install([
  {:statwise, path: Path.expand("..", __DIR__)},
  {:jason, "~> 1.4"},
  {:vega_lite, "~> 0.1"},
  {:kino_vega_lite, "~> 0.1"}
])
```

```elixir
alias Statwise.Visualization
```

## Tutorial Data

```elixir
sample = [1, 2, 2, 3, 4, 5, 5, 5, 6, 7]

groups = %{
  control: [1.2, 1.8, 2.1, 2.4, 2.5],
  treatment: [2.4, 2.8, 3.0, 3.4, 3.9]
}

rows = [
  %{site: :north, treatment: :control, time: 1, score: 1.2},
  %{site: :north, treatment: :control, time: 2, score: 1.8},
  %{site: :north, treatment: :control, time: 3, score: 2.1},
  %{site: :north, treatment: :treated, time: 1, score: 2.4},
  %{site: :north, treatment: :treated, time: 2, score: 2.8},
  %{site: :north, treatment: :treated, time: 3, score: 3.0},
  %{site: :south, treatment: :control, time: 1, score: 1.0},
  %{site: :south, treatment: :control, time: 2, score: 1.4},
  %{site: :south, treatment: :control, time: 3, score: 1.9},
  %{site: :south, treatment: :treated, time: 1, score: 3.0},
  %{site: :south, treatment: :treated, time: 2, score: 3.4},
  %{site: :south, treatment: :treated, time: 3, score: 4.1}
]

normalish = [10.0, 11.5, 12.2, 13.1, 13.8, 15.0]

heatmap_cells = [
  %{metric: :mean, treatment: :control, value: 1.63},
  %{metric: :mean, treatment: :treated, value: 3.08},
  %{metric: :median, treatment: :control, value: 1.8},
  %{metric: :median, treatment: :treated, value: 3.0}
]
```

## Semantic Mappings

Direct constructors use semantic channels such as `x:`, `y:`, `color:`, and
`facet:`:

```elixir
Visualization.box_plot(rows,
  x: :treatment,
  y: :score,
  color: :treatment,
  facet: :site
)
|> Visualization.show()
```

## Relational And Categorical Plots

```elixir
Visualization.scatter(rows, x: :time, y: :score, color: :treatment)
|> Visualization.with_theme(:minimal)
|> Visualization.with_palette(:statwise)
|> Visualization.show()
```

```elixir
Visualization.line(rows, x: :time, y: :score, color: :treatment)
|> Visualization.with_style(width: 420, height: 260)
|> Visualization.show()
```

```elixir
Visualization.bar_plot(rows, x: :treatment, y: :score, stat: :mean)
|> Visualization.show()
```

```elixir
Visualization.point_plot(rows,
  x: :treatment,
  y: :score,
  stat: :mean,
  interval: :confidence,
  confidence_level: 0.95
)
|> Visualization.with_style(width: 420, height: 260)
|> Visualization.show()
```

```elixir
Visualization.bar_plot(rows,
  x: :treatment,
  y: :score,
  stat: :median,
  interval: :percentile,
  confidence_level: 0.5
)
|> Visualization.with_style(width: 420, height: 260)
|> Visualization.show()
```

```elixir
Visualization.count_plot(rows, x: :treatment)
|> Visualization.show()
```

```elixir
Visualization.strip_plot(rows, x: :treatment, y: :score)
|> Visualization.show()
```

```elixir
Visualization.heatmap(heatmap_cells, x: :treatment, y: :metric, color: :value)
|> Visualization.with_palette(["#e0f2fe", "#0284c7"])
|> Visualization.show()
```

## Faceting

Wrapped facets use `facet: :field` and `columns:`:

```elixir
Visualization.box_plot(rows,
  x: :treatment,
  y: :score,
  facet: :site,
  columns: 2
)
|> Visualization.with_style(width: 260, height: 220, color: "red")
|> Visualization.show()
```

Row and column facets use a facet specification:

```elixir
Visualization.scatter(rows,
  x: :time,
  y: :score,
  facet: [row: :site, column: :treatment],
  share_y: false
)
|> Visualization.with_style(width: 180, height: 160)
|> Visualization.show()
```

## Statistical Test Annotations

The usual workflow is to compute the test from the same rows used by the plot.
That keeps filtering, grouping, and faceting aligned with the visual summary.

```elixir
rows
|> Visualization.box_plot(x: :treatment, y: :score)
|> Visualization.with_test(:t_test,
  groups: {:control, :treated},
  show: [:p_value, :effect_size]
)
|> Visualization.with_style(width: 420, height: 260)
|> Visualization.show()
```

Switch to a rank-based comparison without changing the plot:

```elixir
rows
|> Visualization.box_plot(x: :treatment, y: :score)
|> Visualization.with_test(:mann_whitney,
  groups: {:control, :treated},
  show: [:p_value, :effect_size]
)
|> Visualization.with_style(width: 420, height: 260)
|> Visualization.show()
```

You can still attach a precomputed result when the test was run separately:

```elixir
control_scores = for %{treatment: :control, score: score} <- rows, do: score
treated_scores = for %{treatment: :treated, score: score} <- rows, do: score

treatment_result =
  Statwise.TTest.independent(control_scores, treated_scores,
    variance: :welch,
    effect_size: true
  )

rows
|> Visualization.box_plot(x: :treatment, y: :score)
|> Visualization.with_test(treatment_result, groups: {:control, :treated})
|> Visualization.with_style(width: 420, height: 260)
|> Visualization.show()
```

Faceted computed tests run independently inside each facet panel:

```elixir
rows
|> Visualization.box_plot(x: :treatment, y: :score, facet: :site)
|> Visualization.annotate_test(:t_test,
  groups: {:control, :treated},
  show: [:p_value]
)
|> Visualization.with_style(width: 260, height: 220)
|> Visualization.show()
```

## Composition API

```elixir
rows
|> Visualization.plot(x: :time, y: :score, color: :treatment)
|> Visualization.add(:point)
|> Visualization.add(:line)
|> Visualization.label(title: "Layered Scores")
|> Visualization.show()
```

## Styling Model

`with_style/2` attaches presentation choices to a plot without changing the
plot data.

```elixir
styled_histogram =
  Visualization.histogram(sample,
    bins: 6,
    title: "Styled Histogram"
  )
  |> Visualization.with_style(
    width: 420,
    height: 260,
    color: "#2563eb",
    opacity: 0.85,
    background: "#ffffff"
  )

styled_histogram
|> Visualization.show()
```

The underlying data is still available and unchanged:

```elixir
styled_histogram.data
```

### Supported Friendly Style Keys

These shortcuts are mapped into Vega-Lite-compatible locations:

* Top-level/chart layout: `width`, `height`, `background`, `padding`,
  `autosize`, `config`, `view`
* Mark style: `color`, `fill`, `stroke`, `opacity`, `fill_opacity`,
  `stroke_opacity`, `size`, `stroke_width`
* Layer-specific style: `point`, `reference`, `rule`
* Raw mark options: `mark`

In practice:

* `width` and `height` control the chart size. In faceted charts they control
  each panel.
* `config` and `view` are emitted at the top level of the Vega-Lite spec.
* `color`, `opacity`, `size`, and stroke/fill options are merged into the mark.
* `mark: [...]` is merged directly into the mark definition.
* `point: [...]`, `reference: [...]`, and `rule: [...]` target specific layers
  on layered charts.

```elixir
Visualization.ecdf(sample,
  title: "Styled ECDF"
)
|> Visualization.with_style(
  width: 420,
  height: 260,
  color: "#16a34a",
  stroke_width: 3,
  config: [
    axis: [
      labelColor: "#374151",
      titleColor: "#111827"
    ],
    view: [
      stroke: nil
    ]
  ]
)
|> Visualization.show()
```

### Raw Mark Options

Use `mark:` for Vega-Lite mark options that belong inside the mark definition.

```elixir
Visualization.histogram(sample,
  bins: 6,
  title: "Histogram with Mark Options"
)
|> Visualization.with_style(
  width: 420,
  height: 260,
  mark: [
    color: "#2563eb",
    opacity: 0.85,
    tooltip: true
  ]
)
|> Visualization.show()
```

Inspect the generated mark:

```elixir
Visualization.histogram(sample, bins: 6)
|> Visualization.with_style(
  mark: [
    color: "#2563eb",
    opacity: 0.85,
    tooltip: true
  ]
)
|> Visualization.to_vega_lite()
|> Map.get("mark")
```

### Export-Time Style

Style can also be passed only for one export. This is useful when you want the
same plot content rendered in multiple ways.

```elixir
base_ecdf = Visualization.ecdf(sample, title: "Export-Time Style")

base_ecdf
|> Visualization.to_vega_lite(
  style: [
    width: 420,
    height: 260,
    color: "#16a34a",
    stroke_width: 3
  ]
)
```

```elixir
base_ecdf
|> Visualization.show(
  style: [
    width: 420,
    height: 260,
    color: "#dc2626",
    stroke_width: 1
  ]
)
```

### Facet Width and Height

For faceted charts, `width` and `height` control each panel, not the whole
faceted chart. Statwise places them inside the nested Vega-Lite `spec`.

```elixir
Visualization.box_plot(rows,
  x: :treatment,
  y: :score,
  facet: :site,
  columns: 2
)
|> Visualization.with_style(width: 180, height: 220)
|> Visualization.to_vega_lite()
|> Map.take(["facet", "spec"])
```

### Layer-Specific Style

Layered plots can route style to a specific layer. QQ plots use `point:` for
sample points and `reference:` for the reference line.

```elixir
Visualization.qq_plot(normalish,
  title: "Layer-Specific QQ Style"
)
|> Visualization.with_style(
  point: [
    color: "#dc2626",
    size: 80
  ],
  reference: [
    color: "#111827",
    stroke_width: 1
  ],
  width: 420,
  height: 260
)
|> Visualization.show()
```

### Vega-Lite Escape Hatches

`with_style/2` accepts arbitrary nested maps and keyword lists for supported
Vega-Lite routing points including top-level keys, `mark:`, `encoding:`, and
layer-specific keys such as `point:`, `reference:`, and `rule:`.

When in doubt, inspect the generated spec:

```elixir
Visualization.histogram(sample, bins: 6)
|> Visualization.with_style(
  width: 420,
  height: 260,
  mark: [tooltip: true],
  config: [axis: [labelColor: "#374151"]]
)
|> Visualization.to_vega_lite()
```

For example, this custom key is stored on the plot:

```elixir
plot_with_unknown_style =
  Visualization.histogram(sample, bins: 6)
  |> Visualization.with_style(custom_vega_lite_key: [enabled: true])

plot_with_unknown_style.style
```

But it is not emitted into the Vega-Lite spec, because the exporter does not yet
know where that key belongs:

```elixir
plot_with_unknown_style
|> Visualization.to_vega_lite()
|> Map.has_key?("custom_vega_lite_key")
```

## Chart Gallery

The remaining sections show each plot type with a small, runnable example.

## Histogram

```elixir
Visualization.histogram(sample,
  bins: 6,
  title: "Histogram"
)
|> Visualization.with_style(
  width: 420,
  height: 260,
  color: "#2563eb",
  opacity: 0.85
)
|> Visualization.show()
```

```elixir
Visualization.histogram(%{height: [62, 64, 66, nil, 70, 71]},
  x: :height,
  bins: 4,
  x_title: "Height",
  y_title: "Observations",
  title: "Histogram from a Column"
)
|> Visualization.with_style(width: 420, height: 260, color: "#0f766e")
|> Visualization.show()
```

## Box Plot

```elixir
Visualization.box_plot(groups,
  title: "Grouped Box Plot"
)
|> Visualization.with_style(
  width: 420,
  height: 260,
  color: "#7c3aed"
)
|> Visualization.show()
```

## Faceted Box Plot

```elixir
Visualization.box_plot(rows,
  x: :treatment,
  y: :score,
  facet: :site,
  columns: 2,
  title: "Scores by Treatment and Site"
)
|> Visualization.with_style(
  width: 280,
  height: 220,
  color: "#2563eb",
  config: [
    axis: [
      labelColor: "#374151",
      titleColor: "#111827"
    ],
    view: [
      stroke: nil
    ]
  ]
)
|> Visualization.show()
```

## ECDF

```elixir
Visualization.ecdf(sample,
  title: "Empirical CDF"
)
|> Visualization.with_style(
  width: 420,
  height: 260,
  color: "#16a34a",
  stroke_width: 3
)
|> Visualization.show()
```

## Normal QQ Plot

The default reference line is scaled to the sample mean and sample standard deviation.

```elixir
Visualization.qq_plot(normalish,
  title: "Normal QQ Plot"
)
|> Visualization.with_style(
  color: "#dc2626",
  size: 80,
  width: 420,
  height: 260
)
|> Visualization.show()
```

Use `reference_scale: :standard` to show the unscaled standard-normal line.

```elixir
Visualization.qq_plot(normalish,
  reference_scale: :standard,
  title: "QQ Plot with Standard Reference Line"
)
|> Visualization.with_style(
  point: [color: "#dc2626", size: 80],
  reference: [color: "#6b7280", stroke_width: 1],
  width: 420,
  height: 260
)
|> Visualization.show()
```

## Rank Plot

```elixir
Visualization.rank_plot(
  [10, 30, 40],
  [20, 25, 50],
  x_label: :control,
  y_label: :treatment,
  title: "Rank Plot"
)
|> Visualization.with_style(
  width: 420,
  height: 260,
  size: 90,
  color: "#0f766e"
)
|> Visualization.show()
```

## Confidence Interval Plot

```elixir
one_sample_result =
  Statwise.TTest.one_sample([2.5, 3.1, 3.6, 4.0],
    mean: 3.0
  )

Visualization.confidence_interval(one_sample_result,
  title: "One-Sample Mean Confidence Interval"
)
|> Visualization.with_style(
  width: 420,
  height: 160,
  color: "#0f766e",
  size: 80
)
|> Visualization.show()
```

## T-Test Result Plot

```elixir
t_test_result =
  Statwise.TTest.independent(
    [1.2, 1.9, 2.4, 2.9],
    [2.2, 3.0, 3.4, 4.1],
    variance: :welch
  )

Visualization.t_test(t_test_result,
  title: "Welch T-Test Mean Difference"
)
|> Visualization.with_style(
  width: 420,
  height: 160,
  color: "#9333ea",
  size: 80
)
|> Visualization.show()
```

## Mann-Whitney U Plot

```elixir
mann_whitney_result =
  Statwise.MannWhitney.test(
    [1.0, 3.0, 5.0],
    [2.0, 4.0, 6.0],
    method: :auto
  )

Visualization.mann_whitney(mann_whitney_result,
  title: "Mann-Whitney U"
)
|> Visualization.with_style(
  width: 420,
  height: 260,
  color: "#ea580c"
)
|> Visualization.show()
```

```elixir
mann_whitney_result
```

## Export Helpers

Use `to_vega_lite/1` to inspect the generated Vega-Lite map.

```elixir
Visualization.box_plot(rows,
  x: :treatment,
  y: :score,
  facet: :site,
  columns: 2
)
|> Visualization.with_style(width: 180, height: 220)
|> Visualization.to_vega_lite()
```

Use `to_json/1` when you need encoded Vega-Lite JSON.

```elixir
Visualization.histogram(sample, bins: 6)
|> Visualization.to_json()
```
