Drafter.Widget.PieChart (drafter v0.5.0)

Copy Markdown View Source

Renders a circular pie chart using Unicode block and braille characters.

Each slice is proportional to its value relative to the total. The chart uses quarter-block and half-block Unicode characters for sub-cell resolution rendering, and braille characters where finer detail is needed.

Component tag

Tag :pie_chart, built by Drafter.App as {:pie_chart, data, opts}:

pie_chart(data, opts)

The positional argument becomes :data when it is a non-empty list that is not a keyword list; otherwise :data is read from opts.

Options

  • :data - [{label, value} | {label, value, rgb}]. Default []. A slice without an explicit colour takes the palette entry at its index, cycling. A total of zero gives every slice a percentage of 0.0.

  • :show_legend - boolean/0, draw labels down the right-hand side. Default true. The legend takes min(longest_entry + 4, 30) columns and the pie takes the rest, with at least one column.
  • :show_percentages - boolean/0, append (12.5%) to each legend label. Default true. Only read when the legend is drawn.
  • :colors - [{r, g, b}] palette. Default [{100, 180, 255}, {255, 130, 80}, {100, 220, 140}, {220, 100, 220}, {255, 220, 80}, {120, 220, 220}, {255, 100, 100}, {180, 140, 255}].
  • :renderer - :text (default) draws block and braille cells. Any other atom is treated as a terminal graphics protocol and passed to Drafter.Widget.Chart.Pixel, falling back to cells when that protocol is unavailable.
  • :style - map/0 of style overrides passed to the theme computation. Default %{}.
  • :class - theme class atom or list of them, normalised by Drafter.Style.normalize_classes/1 and reaching mount/1 as :classes. Default [].
  • :height - pos_integer/0 read only by preferred_height/2, never by mount/1. Default 10.

Every option except :height is live-updatable: update_props_from_mount/3 passes the full mount props through.

Usage

pie_chart([{"Elixir", 45}, {"Rust", 30}, {"Go", 25}])
pie_chart([{"A", 60, {255, 100, 100}}, {"B", 40, {100, 100, 255}}])
pie_chart([{"X", 10}, {"Y", 20}], show_legend: false)

Summary

Functions

Replaces :data with a non-empty list of slice entries.

The component tag this widget registers under.

Builds the props map for a {:pie_chart, data, opts} element.

Ignores every event and returns {:bubble, state}, letting it continue to the parent widget and then to the app.

Whether this pie chart is drawing a transmitted image rather than cells.

The pie, without its legend, as a Drafter.Render.Picture at the left of rect, or nil when the pie is not drawn as an image.

Builds the widget state from props.

The number of rows the element asks for: opts[:height], default 10.

Draws the chart into rect, always returning exactly rect.height strips.

Callback implementation for Drafter.Widget.unmount/1.

Replaces the state fields named in props, keeping the current value for any key that is absent.

Passes the mount props through unchanged, so every option is live-updatable through the component tree.

Types

entry()

@type entry() :: {String.t(), number()} | {String.t(), number(), rgb()}

rgb()

@type rgb() :: {0..255, 0..255, 0..255}

t()

@type t() :: %Drafter.Widget.PieChart{
  app_module: module() | nil,
  classes: [atom()],
  colors: [rgb()],
  data: [entry()],
  renderer: atom(),
  show_legend: boolean(),
  show_percentages: boolean(),
  style: map()
}

Functions

apply_data_buffer(state, data, rect)

@spec apply_data_buffer(t(), [entry() | term()], Drafter.Widget.rect() | nil) :: t()

Replaces :data with a non-empty list of slice entries.

Unlike the other data-driven widgets this expects a plain list rather than a Drafter.RingBuffer: {label, value} and {label, value, color} tuples pass through, and anything else becomes {"", term}. An empty list or a value that is not a list returns state unchanged.

iex> state = Drafter.Widget.PieChart.mount(%{})
iex> Drafter.Widget.PieChart.apply_data_buffer(state, [{"A", 1}, 7], nil).data
[{"A", 1}, {"", 7}]

iex> state = Drafter.Widget.PieChart.mount(%{data: [{"A", 1}]})
iex> Drafter.Widget.PieChart.apply_data_buffer(state, [], nil).data
[{"A", 1}]

component_tag()

@spec component_tag() :: :pie_chart

The component tag this widget registers under.

iex> Drafter.Widget.PieChart.component_tag()
:pie_chart

focused(state)

from_component_opts(data, opts)

@spec from_component_opts(
  term(),
  keyword()
) :: Drafter.Widget.props()

Builds the props map for a {:pie_chart, data, opts} element.

data becomes :data when it is a non-empty list that is not a keyword list; otherwise opts[:data] is used, defaulting to []. :class is normalised into :classes and :__app_module__ becomes :app_module.

iex> props = Drafter.Widget.PieChart.from_component_opts([{"A", 1}], show_legend: false)
iex> {props.data, props.show_legend, props.renderer}
{[{"A", 1}], false, :text}

iex> Drafter.Widget.PieChart.from_component_opts(nil, data: [{"B", 2}]).data
[{"B", 2}]

handle_event(event, state)

@spec handle_event(Drafter.Event.t(), t()) :: {:bubble, t()}

Ignores every event and returns {:bubble, state}, letting it continue to the parent widget and then to the app.

image_active?(state)

@spec image_active?(t()) :: boolean()

Whether this pie chart is drawing a transmitted image rather than cells.

image_raster(state, rect)

@spec image_raster(t() | Drafter.Widget.props(), Drafter.Widget.rect()) ::
  Drafter.Render.Picture.t() | nil

The pie, without its legend, as a Drafter.Render.Picture at the left of rect, or nil when the pie is not drawn as an image.

mount(props)

@spec mount(Drafter.Widget.props()) :: t()

Builds the widget state from props.

Every option listed in the module doc is read here with the default stated there.

iex> state = Drafter.Widget.PieChart.mount(%{data: [{"A", 60}, {"B", 40}]})
iex> {state.data, state.show_legend, state.show_percentages, state.renderer}
{[{"A", 60}, {"B", 40}], true, true, :text}

iex> Drafter.Widget.PieChart.mount(%{}).colors |> length()
8

preferred_height(args, opts)

@spec preferred_height(
  term(),
  keyword()
) :: pos_integer()

The number of rows the element asks for: opts[:height], default 10.

iex> Drafter.Widget.PieChart.preferred_height(nil, [])
10

iex> Drafter.Widget.PieChart.preferred_height(nil, height: 20)
20

render(state, rect)

Draws the chart into rect, always returning exactly rect.height strips.

state may be a plain props map, in which case it is passed through mount/1 first. Slices are laid out clockwise starting at twelve o'clock. This is the cell renderer and is used whatever :renderer says; image/3 is the graphics path.

unmount(state)

Callback implementation for Drafter.Widget.unmount/1.

update(props, state)

@spec update(Drafter.Widget.props(), t()) :: t()

Replaces the state fields named in props, keeping the current value for any key that is absent.

Accepts :data, :show_legend, :show_percentages, :colors, :style, :classes, :app_module and :renderer.

iex> state = Drafter.Widget.PieChart.mount(%{data: [{"A", 1}]})
iex> updated = Drafter.Widget.PieChart.update(%{show_legend: false}, state)
iex> {updated.data, updated.show_legend}
{[{"A", 1}], false}

update_props_from_mount(mount_props, existing_state, opts)

@spec update_props_from_mount(Drafter.Widget.props(), term(), keyword()) ::
  Drafter.Widget.props()

Passes the mount props through unchanged, so every option is live-updatable through the component tree.

iex> props = Drafter.Widget.PieChart.from_component_opts([{"A", 1}], [])
iex> Drafter.Widget.PieChart.update_props_from_mount(props, %{}, []) == props
true