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")
])

Summary

Functions

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

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

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

Points at x and y, sized by size.

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

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".

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

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.

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

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

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

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

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.

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

One number, large, with a label.

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

One number on a bar towards a goal.

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={...}>.

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

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

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

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

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

Text at x and y.

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

One number with its change from compare.

Fude's version.

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.

Functions

annotate(chart, type, opts)

@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(opts_or_data)

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

area(data, opts)

@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(opts_or_data)

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

bar(data, opts)

@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(opts_or_data)

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

bubble(data, opts)

@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(opts_or_data)

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

bubble_map(data, opts)

@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(opts_or_data)

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

choropleth(data, opts)

@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(opts_or_data)

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

donut(data, opts)

@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(chart, field, opts \\ [])

@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(opts_or_data)

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

funnel(data, opts)

@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(opts_or_data)

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

gauge(data, opts)

@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(opts_or_data)

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

heatmap(data, opts)

@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(opts_or_data)

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

histogram(data, opts)

@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(data, charts, opts \\ [])

@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(opts_or_data)

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

line(data, opts)

@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(opts_or_data)

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

number(data, opts)

@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(opts_or_data)

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

pie(data, opts)

@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(opts_or_data)

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

progress(data, opts)

@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(chart, data)

@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(chart, name, data)

@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(queryable, repo, opts \\ [])

@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(opts_or_data)

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

rule(data, opts)

@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(opts_or_data)

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

scatter(data, opts)

@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(opts_or_data)

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

sparkline(data, opts)

@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(opts_or_data)

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

text(data, opts)

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

Text at x and y.

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

title(chart, title, opts \\ [])

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

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

trend(opts_or_data)

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

trend(data, opts)

@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()

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

Fude's version.

waterfall(opts_or_data)

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

waterfall(data, opts)

@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