Current section
Files
Jump to
Current section
Files
lib/plotex/view_range.ex
defmodule Plotex.ViewRange do
alias __MODULE__
@unix_epoch ~N[1970-01-01 00:00:00]
defstruct start: 10,
stop: 90,
projection: :cartesian
@type t :: %Plotex.ViewRange{start: number(), stop: number(), projection: :cartesian | :polar}
def new({a, b}, proj \\ :cartesian) do
%ViewRange{start: a, stop: b, projection: proj}
end
def empty(proj \\ :cartesian) do
%ViewRange{start: nil, stop: nil, projection: proj}
end
@doc """
Find the maximum and minumun points for a given line of data.
"""
def from(data, proj \\ :cartesian) do
unless Enum.count(data) == 0 do
{a, b} = Enum.min_max_by(data, &Plotex.ViewRange.convert/1)
%ViewRange{start: a, stop: b, projection: proj}
else
%ViewRange{start: nil, stop: nil, projection: proj}
end
end
def min_max(%{start: nil, stop: nil}, b), do: b
def min_max(a, %{start: nil, stop: nil}), do: a
def min_max(va, vb) do
start! = Enum.min_by([va.start, vb.start], &convert/1)
stop! = Enum.max_by([va.stop, vb.stop], &convert/1)
%ViewRange{start: start!, stop: stop!, projection: va.projection}
end
def convert(nil), do: nil
def convert(%Time{} = val), do: Time.to_erl(val)
def convert(%Date{} = val), do: Date.to_erl(val)
def convert(%DateTime{} = val), do: DateTime.to_unix(val, :nanosecond)
def convert(%NaiveDateTime{} = a), do: NaiveDateTime.diff(a, @unix_epoch, :nanosecond)
def convert(val) when is_number(val), do: val
def to_val(a, units \\ :nanosecond), do: vals(a, units)
def vals(%Date{} = a, _units), do: Date.diff(a, @unix_epoch)
def vals(%DateTime{} = a, units), do: DateTime.to_unix(a, units)
def vals(%NaiveDateTime{} = a, units), do: NaiveDateTime.diff(a, @unix_epoch, units)
def vals(a, _units), do: a
def diff(%DateTime{} = b, %DateTime{} = a), do: DateTime.diff(b, a, :nanosecond)
def diff(%NaiveDateTime{} = b, %NaiveDateTime{} = a), do: NaiveDateTime.diff(b, a, :nanosecond)
def diff(b, a), do: b - a
def pad(%ViewRange{start: start, stop: stop, projection: proj}, _opts)
when is_nil(start) or is_nil(stop) do
%ViewRange{start: nil, stop: nil, projection: proj}
end
def pad(%ViewRange{start: %DateTime{} = start, stop: %DateTime{} = stop} = vr, opts) do
amount = Keyword.get(opts, :padding, 0.05) * ViewRange.dist(vr)
%ViewRange{
start: start |> DateTime.add(-round(amount), :nanosecond),
stop: stop |> DateTime.add(round(amount), :nanosecond)
}
end
def pad(%ViewRange{start: %NaiveDateTime{} = start, stop: %NaiveDateTime{} = stop} = vr, opts) do
amount = Keyword.get(opts, :padding, 0.05) * ViewRange.dist(vr)
%ViewRange{
start: start |> NaiveDateTime.add(-round(amount), :nanosecond),
stop: stop |> NaiveDateTime.add(round(amount), :nanosecond)
}
end
def pad(%ViewRange{start: start, stop: stop, projection: proj} = vr, opts) do
amount = Keyword.get(opts, :padding, 0.05) * ViewRange.dist(vr)
%ViewRange{start: start - amount, stop: stop + amount, projection: proj}
end
def dist({start, stop}) when is_nil(start) or is_nil(stop) do
1.0
end
@type datetime :: DateTime.t() | NaiveDateTime.t()
@spec dist({datetime(), datetime()} | {nil, nil} | ViewRange.t()) :: number
def dist({%{} = start, %{} = stop}) do
diff = to_val(stop) - to_val(start)
if diff != 0 do
diff
else
1_000_000_000
end
end
def dist({start, stop}) do
if stop != start do
stop - start
else
1.0
end
end
def dist(%ViewRange{} = range) do
dist({range.start, range.stop})
end
end