Packages

Contex - a server-side charting library for Elixir.

Current section

Files

Jump to
contex lib chart scale ordinal_scale.ex
Raw

lib/chart/scale/ordinal_scale.ex

defmodule Contex.OrdinalScale do
alias __MODULE__
defstruct [:domain, :range, :padding, :domain_to_range_fn, :range_to_domain_fn, :domain_to_range_band_fn]
def new(domain) when is_list(domain) do
%OrdinalScale{domain: domain, padding: 0.5}
end
def domain(%OrdinalScale{} = scale, data) when is_list(data) do
%{scale | domain: data}
|> update_transform_funcs
end
def padding(%OrdinalScale{} = scale, padding) when is_number(padding) do
# We need to update the transform functions if we change the padding as the band calculations need it
%{scale | padding: padding}
|> update_transform_funcs
end
def update_transform_funcs(%OrdinalScale{domain: domain, range: {start_r, end_r}, padding: padding} = scale)
when is_list(domain) and is_number(start_r) and is_number(end_r) and is_number(padding)
do
domain_count = Kernel.length(domain)
range_width = end_r - start_r
item_width = case domain_count do
0 -> 0.0
_ -> range_width / domain_count
end
flip_padding = case start_r < end_r do
true -> 1.0
_ -> -1.0
end
# Returns centre point of bucket
domain_to_range_fn =
fn domain_val ->
case Enum.find_index(domain, fn x -> x == domain_val end) do
nil -> start_r
index ->
start_r + (item_width / 2.0) + (index * item_width)
end
end
domain_to_range_band_fn =
fn domain_val ->
case Enum.find_index(domain, fn x -> x == domain_val end) do
nil -> {start_r, start_r}
index ->
band_start = start_r + (flip_padding * padding / 2.0) + (index * item_width)
band_end = start_r + ((index + 1) * item_width) - (flip_padding * padding / 2.0)
{band_start, band_end}
end
end
range_to_domain_fn = case range_width do
0 -> fn -> "" end
_ ->
fn range_val ->
case domain_count do
0 -> ""
_ ->
bucket_index = Kernel.trunc((range_val - start_r) / item_width)
Enum.at(domain, bucket_index)
end
end
end
%{scale | domain_to_range_fn: domain_to_range_fn, range_to_domain_fn: range_to_domain_fn, domain_to_range_band_fn: domain_to_range_band_fn}
end
def update_transform_funcs(%OrdinalScale{} = scale), do: scale
def get_band(%OrdinalScale{domain_to_range_band_fn: domain_to_range_band_fn}, domain_value)
when is_function(domain_to_range_band_fn) do
domain_to_range_band_fn.(domain_value)
end
defimpl Contex.Scale do
@spec ticks_domain(Contex.OrdinalScale.t()) :: any
def ticks_domain(%OrdinalScale{domain: domain}), do: domain
def ticks_range(%OrdinalScale{domain_to_range_fn: transform_func} = scale) when is_function(transform_func) do
ticks_domain(scale)
|> Enum.map(transform_func)
end
def domain_to_range_fn(%OrdinalScale{domain_to_range_fn: domain_to_range_fn}), do: domain_to_range_fn
def domain_to_range(%OrdinalScale{domain_to_range_fn: transform_func}, range_val) when is_function(transform_func) do
transform_func.(range_val)
end
def get_range(%OrdinalScale{range: {min_r, max_r}}), do: {min_r, max_r}
def set_range(%OrdinalScale{} = scale, start, finish) when is_number(start) and is_number(finish) do
%{scale | range: {start, finish}}
|> OrdinalScale.update_transform_funcs
end
def set_range(%OrdinalScale{} = scale, {start, finish}) when is_number(start) and is_number(finish), do: set_range(scale, start, finish)
def get_formatted_tick(_, tick_val), do: tick_val
end
end