Renders a compact sparkline chart using Unicode block characters.
Each data point maps to one of the nine bar heights ▁▂▃▄▅▆▇█ (or a blank
for the minimum). When :min_color and :max_color differ, individual bars
are coloured by linear interpolation between those two colours based on their
normalised value. An optional summary appends min:X max:Y avg:Z text to the
right of the bars.
When orientation: :horizontal is set, each data point becomes one row and
bars grow left-to-right using left-aligned eighth-block characters.
Component tag
Tag :sparkline, built by Drafter.App as {:sparkline, data, opts}:
sparkline(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. Both
sparkline(values, summary: true) and sparkline(data: values, summary: true)
are therefore valid.
Options
:data-[number()]to plot. Default[]. Only the firstwidthvalues of a vertical sparkline and the firstrect.heightvalues of a horizontal one are drawn.:min_value-number/0explicit minimum for scaling. Default:Enum.min(data), or0when the data is empty. Anilvalue falls back to the same default.:max_value-number/0explicit maximum for scaling. Default:Enum.max(data), or0when the data is empty. Anilvalue falls back to the same default.:min_color-{r, g, b}colour for the lowest bars. Defaultnil, which uses the sparkline's computed theme colour, itself falling back to{100, 200, 100}.:max_color-{r, g, b}colour for the highest bars. Defaultnil, with the same fallback as:min_color. Equal min and max colours make every bar that colour.:color-{r, g, b}. Defaultnil. Held on the state and never read byrender/2, which takes its base colour from the theme.:summary-boolean/0, appendmin:X max:Y avg:Zat the right edge. Defaultfalse. Reserves 20 columns of the rect for the text. Only drawn for a vertical sparkline, thoughapply_data_buffer/3reserves the same 20 columns either way.:orientation-:vertical | :horizontal. Default:vertical; any value other than:horizontalrenders vertically.:style-map/0of style overrides passed to the theme computation. Default%{}.:class- theme class atom or list of them, normalised byDrafter.Style.normalize_classes/1and reachingmount/1as:classes. Default[].:height-pos_integer/0read only bypreferred_height/2, never bymount/1. Default3.
Every option except :height is live-updatable: update_props_from_mount/3
passes the full mount props through. Supplying :data without :min_value or
:max_value rescales the sparkline to the new data.
Usage
sparkline(data: [1, 3, 2, 8, 5, 9, 4], summary: true)
sparkline(data: readings, min_color: {100, 200, 100}, max_color: {255, 50, 50})
sparkline(data: readings, orientation: :horizontal)
Summary
Functions
Replaces :data with the newest entries of a Drafter.RingBuffer and rescales
:min_value and :max_value to them.
The component tag this widget registers under.
Builds the props map for a {:sparkline, data, opts} element.
Ignores every event and returns {:noreply, state}.
Blends two {r, g, b} colours, rounding each channel.
Builds the widget state from props.
The number of rows the element asks for: opts[:height], default 3.
Draws the sparkline into rect.
Turns data into {bar_characters, normalized_values}.
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
@type rgb() :: {0..255, 0..255, 0..255}
Functions
@spec apply_data_buffer(t(), Drafter.RingBuffer.t(), Drafter.Widget.rect()) :: t()
Replaces :data with the newest entries of a Drafter.RingBuffer and rescales
:min_value and :max_value to them.
Takes the last rect.width values, or rect.width - 20 when :summary is set,
with a floor of one value. Returns state unchanged for an empty buffer, which
is the only case where an explicit :min_value or :max_value survives.
@spec component_tag() :: :sparkline
The component tag this widget registers under.
iex> Drafter.Widget.Sparkline.component_tag()
:sparkline
@spec from_component_opts( term(), keyword() ) :: Drafter.Widget.props()
Builds the props map for a {:sparkline, 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.Sparkline.from_component_opts([1, 3, 2], summary: true)
iex> {props.data, props.summary, props.min_value}
{[1, 3, 2], true, nil}
iex> props = Drafter.Widget.Sparkline.from_component_opts([data: [4, 5]], [])
iex> props.data
[]
iex> Drafter.Widget.Sparkline.from_component_opts(nil, data: [4, 5]).data
[4, 5]
@spec handle_event(Drafter.Event.t(), t() | Drafter.Widget.props()) :: {:noreply, t()}
Ignores every event and returns {:noreply, state}.
A plain props map is passed through mount/1 first, so the returned state is
always a t/0. The sparkline is not focusable.
Blends two {r, g, b} colours, rounding each channel.
factor must be a float — an integer raises FunctionClauseError. 0.0 returns
the first colour and 1.0 the second; values outside 0.0..1.0 extrapolate.
iex> Drafter.Widget.Sparkline.interpolate_color({0, 0, 0}, {200, 100, 50}, 0.5)
{100, 50, 25}
iex> Drafter.Widget.Sparkline.interpolate_color({10, 20, 30}, {200, 100, 50}, 0.0)
{10, 20, 30}
@spec mount(Drafter.Widget.props()) :: t()
Builds the widget state from props.
:min_value and :max_value are taken from props when present and not nil,
and otherwise from the data — Enum.min/1 and Enum.max/1, or 0 and 0 for
empty data.
iex> state = Drafter.Widget.Sparkline.mount(%{data: [1, 3, 2, 8]})
iex> {state.min_value, state.max_value, state.summary, state.orientation}
{1, 8, false, :vertical}
iex> state = Drafter.Widget.Sparkline.mount(%{})
iex> {state.data, state.min_value, state.max_value}
{[], 0, 0}
iex> Drafter.Widget.Sparkline.mount(%{data: [1, 2], max_value: 100}).max_value
100
@spec preferred_height( term(), keyword() ) :: pos_integer()
The number of rows the element asks for: opts[:height], default 3.
iex> Drafter.Widget.Sparkline.preferred_height(nil, [])
3
iex> Drafter.Widget.Sparkline.preferred_height([1, 2, 3], height: 8)
8
@spec render(t() | Drafter.Widget.props(), Drafter.Widget.rect()) :: [ Drafter.Draw.Strip.t() ]
Draws the sparkline into rect.
state may be a plain props map, in which case it is passed through mount/1
first. A vertical sparkline returns a single strip. A horizontal one returns one
strip per data point, capped at rect.height, so it returns [] for empty data.
@spec render_sparkline_with_values([number()], number(), number(), non_neg_integer()) :: {String.t(), [float()]}
Turns data into {bar_characters, normalized_values}.
Takes at most width values, normalises each into 0.0..1.0 against min_val
and max_val, and picks the matching character from the current skin's vertical
sparkline levels. Empty data returns width spaces and width values of 0.5.
The two elements of the result always have the same length, which is
min(length(data), width) for non-empty data.
iex> Drafter.Widget.Sparkline.render_sparkline_with_values([], 0, 0, 3)
{" ", [0.5, 0.5, 0.5]}
iex> {chars, values} = Drafter.Widget.Sparkline.render_sparkline_with_values([1, 5, 10], 1, 10, 2)
iex> {String.length(chars), values}
{2, [0.0, 0.4444444444444444]}
Callback implementation for Drafter.Widget.unmount/1.
@spec update(Drafter.Widget.props(), t()) :: t()
Replaces the state fields named in props, keeping the current value for any key
that is absent.
New :data rescales :min_value and :max_value to it unless props carries a
non-nil :min_value or :max_value of its own. Empty new data keeps the
existing scale.
iex> state = Drafter.Widget.Sparkline.mount(%{data: [1, 2, 3]})
iex> updated = Drafter.Widget.Sparkline.update(%{data: [10, 20]}, state)
iex> {updated.data, updated.min_value, updated.max_value}
{[10, 20], 10, 20}
iex> state = Drafter.Widget.Sparkline.mount(%{data: [1, 2, 3]})
iex> updated = Drafter.Widget.Sparkline.update(%{data: [10, 20], max_value: 50}, state)
iex> {updated.min_value, updated.max_value}
{10, 50}
@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.Sparkline.from_component_opts([1, 2], [])
iex> Drafter.Widget.Sparkline.update_props_from_mount(props, %{}, []) == props
true