# `Fude`
[🔗](https://github.com/hattoriai/fude/blob/v0.1.0/lib/fude.ex#L1)

Fude (筆, "brush") draws charts in a Phoenix app.

    Fude.bar(sales, x: :region, y: :revenue)
    Fude.line(visits, x: :day, y: :count, color: :source)
    Fude.histogram(orders, x: :total, bins: 30)
    Fude.number(totals, value: :mrr, format: :compact, label: "MRR")

Each function takes the data and the chart's channels and options, and
gives a `Fude.Chart`, which `<.chart>` draws (`Fude.Components`):

    <.chart id="revenue" chart={Fude.bar(@sales, x: :region, y: :revenue)} />

The data is a list of maps or a map of `:columns` and `:rows` (as a
query result has them). A channel is a field, or a field with what to
do with it (`{:sum, :revenue}`, `{:month, :created_at}`, see
`Fude.Channel`).

Every function also takes the keys of the chart:

  * `:title`, `:subtitle` - above the chart.
  * `:caption`, `:source` - below it, as a graphics desk sets them.
  * `:description` - what a screen reader says of the chart; Fude writes
    one when it is not given.
  * `:height` - the height of the plot in pixels.
  * `:data` - the name of the chart's data in its spec, `"data"` when
    not given (see `Fude.Spec`).

A chart is plain data: `Fude.Spec.dump/1` gives a map to save, and
`Fude.Spec.load/1` takes it back.

## Layers

Charts drawn one over the other share their axes:

    Fude.layers(visits, [
      Fude.area(x: :day, y: :count),
      Fude.line(x: :day, y: :count),
      Fude.rule(y: 1000, label: "Goal")
    ])

# `annotate`

```elixir
@spec annotate(Fude.Chart.t(), Fude.Annotation.type(), keyword()) :: Fude.Chart.t()
```

Writes a note on the chart, at a place of its data (see
`Fude.Annotation`):

    chart
    |> Fude.annotate(:label, x: "North", y: 136_000, text: "Best quarter on record")
    |> Fude.annotate(:band, x: {~D[2026-03-01], ~D[2026-05-01]}, text: "Launch")
    |> Fude.annotate(:line, y: 4000, text: "Goal")

# `area`

```elixir
@spec area(keyword() | term()) :: Fude.Chart.t()
```

# `area`

```elixir
@spec area(term(), keyword()) :: Fude.Chart.t()
```

Areas: as lines, filled down to zero; stacked when they have a `color`.

    Fude.area(data, ...)
    Fude.area(...)          # a layer, for Fude.layers/2

# `bar`

```elixir
@spec bar(keyword() | term()) :: Fude.Chart.t()
```

# `bar`

```elixir
@spec bar(term(), keyword()) :: Fude.Chart.t()
```

Bars: a category on `x` and a number on `y`; `horizontal: true` turns them.

    Fude.bar(data, ...)
    Fude.bar(...)          # a layer, for Fude.layers/2

# `bubble`

```elixir
@spec bubble(keyword() | term()) :: Fude.Chart.t()
```

# `bubble`

```elixir
@spec bubble(term(), keyword()) :: Fude.Chart.t()
```

Points at `x` and `y`, sized by `size`.

    Fude.bubble(data, ...)
    Fude.bubble(...)          # a layer, for Fude.layers/2

# `bubble_map`

```elixir
@spec bubble_map(keyword() | term()) :: Fude.Chart.t()
```

# `bubble_map`

```elixir
@spec bubble_map(term(), keyword()) :: Fude.Chart.t()
```

A map with a circle at each `region`, sized by `size`.

    Fude.bubble_map(data, ...)
    Fude.bubble_map(...)          # a layer, for Fude.layers/2

# `choropleth`

```elixir
@spec choropleth(keyword() | term()) :: Fude.Chart.t()
```

# `choropleth`

```elixir
@spec choropleth(term(), keyword()) :: Fude.Chart.t()
```

A map of regions colored by `color`: each `region` an ISO code (`"PRT"`, `"PT"`), a number code or a name; `map` is `"world"` (the default) or `"us-states"`.

    Fude.choropleth(data, ...)
    Fude.choropleth(...)          # a layer, for Fude.layers/2

# `donut`

```elixir
@spec donut(keyword() | term()) :: Fude.Chart.t()
```

# `donut`

```elixir
@spec donut(term(), keyword()) :: Fude.Chart.t()
```

A donut: a pie with a hole (`inner`, the hole's share of the radius).

    Fude.donut(data, ...)
    Fude.donut(...)          # a layer, for Fude.layers/2

# `facet`

```elixir
@spec facet(Fude.Chart.t(), atom() | String.t(), keyword()) :: Fude.Chart.t()
```

Small multiples: the chart drawn once for each value of a field, side
by side, on the same scales, so that the panels compare at a glance.

    Fude.line(sales, x: :month, y: :revenue)
    |> Fude.facet(:region, columns: 3)

`columns` is how many panels make a row; as many as fit when not given.

# `funnel`

```elixir
@spec funnel(keyword() | term()) :: Fude.Chart.t()
```

# `funnel`

```elixir
@spec funnel(term(), keyword()) :: Fude.Chart.t()
```

A funnel: the stages on `x` with their `y`, each with its share of the first.

    Fude.funnel(data, ...)
    Fude.funnel(...)          # a layer, for Fude.layers/2

# `gauge`

```elixir
@spec gauge(keyword() | term()) :: Fude.Chart.t()
```

# `gauge`

```elixir
@spec gauge(term(), keyword()) :: Fude.Chart.t()
```

One number on an arc from `min` to `max` (0 and 100 when not given), with a `target`.

    Fude.gauge(data, ...)
    Fude.gauge(...)          # a layer, for Fude.layers/2

# `heatmap`

```elixir
@spec heatmap(keyword() | term()) :: Fude.Chart.t()
```

# `heatmap`

```elixir
@spec heatmap(term(), keyword()) :: Fude.Chart.t()
```

A heatmap: a cell for each `x` and `y`, colored by `color`.

    Fude.heatmap(data, ...)
    Fude.heatmap(...)          # a layer, for Fude.layers/2

# `histogram`

```elixir
@spec histogram(keyword() | term()) :: Fude.Chart.t()
```

# `histogram`

```elixir
@spec histogram(term(), keyword()) :: Fude.Chart.t()
```

A histogram: the numbers of `x` counted in bins (`bins`, about how many).

    Fude.histogram(data, ...)
    Fude.histogram(...)          # a layer, for Fude.layers/2

# `layers`

```elixir
@spec layers(term(), [Fude.Chart.t()], keyword()) :: Fude.Chart.t()
```

A chart of layers drawn one over the other, on the same axes: the first
at the back. Each layer is a chart of one kind, made without data; the
layers read the same data.

# `line`

```elixir
@spec line(keyword() | term()) :: Fude.Chart.t()
```

# `line`

```elixir
@spec line(term(), keyword()) :: Fude.Chart.t()
```

Lines: a time or a number on `x`, a number on `y`, a line for each `color`.

    Fude.line(data, ...)
    Fude.line(...)          # a layer, for Fude.layers/2

# `number`

```elixir
@spec number(keyword() | term()) :: Fude.Chart.t()
```

# `number`

```elixir
@spec number(term(), keyword()) :: Fude.Chart.t()
```

One number, large, with a `label`.

    Fude.number(data, ...)
    Fude.number(...)          # a layer, for Fude.layers/2

# `pie`

```elixir
@spec pie(keyword() | term()) :: Fude.Chart.t()
```

# `pie`

```elixir
@spec pie(term(), keyword()) :: Fude.Chart.t()
```

A pie: a slice for each `color`, as large as its `value`.

    Fude.pie(data, ...)
    Fude.pie(...)          # a layer, for Fude.layers/2

# `progress`

```elixir
@spec progress(keyword() | term()) :: Fude.Chart.t()
```

# `progress`

```elixir
@spec progress(term(), keyword()) :: Fude.Chart.t()
```

One number on a bar towards a `goal`.

    Fude.progress(data, ...)
    Fude.progress(...)          # a layer, for Fude.layers/2

# `put_data`

```elixir
@spec put_data(Fude.Chart.t(), term()) :: Fude.Chart.t()
```

Gives the chart its data, under its data's name. A chart loaded from a
spec gets its data again this way, or from `<.chart data={...}>`.

# `put_data`

```elixir
@spec put_data(Fude.Chart.t(), String.t(), term()) :: Fude.Chart.t()
```

Gives the chart a dataset of a name, for a layer that reads it.

# `query`

```elixir
@spec query(Ecto.Queryable.t(), module(), keyword()) :: Fude.Query.t()
```

The data of an Ecto query or schema, grouped and aggregated in the
database when the chart draws (see `Fude.Query`):

    Order |> Fude.query(Repo) |> Fude.bar(x: :status, y: {:count, :id})

# `rule`

```elixir
@spec rule(keyword() | term()) :: Fude.Chart.t()
```

# `rule`

```elixir
@spec rule(term(), keyword()) :: Fude.Chart.t()
```

A rule across the chart at `x` or at `y`, with a `label`.

    Fude.rule(data, ...)
    Fude.rule(...)          # a layer, for Fude.layers/2

# `scatter`

```elixir
@spec scatter(keyword() | term()) :: Fude.Chart.t()
```

# `scatter`

```elixir
@spec scatter(term(), keyword()) :: Fude.Chart.t()
```

Points at `x` and `y`, colored by `color` and sized by `size`.

    Fude.scatter(data, ...)
    Fude.scatter(...)          # a layer, for Fude.layers/2

# `sparkline`

```elixir
@spec sparkline(keyword() | term()) :: Fude.Chart.t()
```

# `sparkline`

```elixir
@spec sparkline(term(), keyword()) :: Fude.Chart.t()
```

A small line with no axes, for a table or a card.

    Fude.sparkline(data, ...)
    Fude.sparkline(...)          # a layer, for Fude.layers/2

# `text`

```elixir
@spec text(keyword() | term()) :: Fude.Chart.t()
```

# `text`

```elixir
@spec text(term(), keyword()) :: Fude.Chart.t()
```

Text at `x` and `y`.

    Fude.text(data, ...)
    Fude.text(...)          # a layer, for Fude.layers/2

# `title`

```elixir
@spec title(Fude.Chart.t(), String.t(), keyword()) :: Fude.Chart.t()
```

Sets the chart's title, and with `:subtitle`, its subtitle.

# `trend`

```elixir
@spec trend(keyword() | term()) :: Fude.Chart.t()
```

# `trend`

```elixir
@spec trend(term(), keyword()) :: Fude.Chart.t()
```

One number with its change from `compare`.

    Fude.trend(data, ...)
    Fude.trend(...)          # a layer, for Fude.layers/2

# `version`

```elixir
@spec version() :: String.t()
```

Fude's version.

# `waterfall`

```elixir
@spec waterfall(keyword() | term()) :: Fude.Chart.t()
```

# `waterfall`

```elixir
@spec waterfall(term(), keyword()) :: Fude.Chart.t()
```

A waterfall: how each change of `y` (a step on `x`) moves a running total; `total` adds a bar of the end, `start` names the first bar as a total.

    Fude.waterfall(data, ...)
    Fude.waterfall(...)          # a layer, for Fude.layers/2

---

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