This repository has been archived on 2024-06-24. You can view files and clone it, but cannot push or open issues or pull requests.
augie/webapp/lib/augie_web/components/sparkline_component.ex

103 lines
3.1 KiB
Elixir
Raw Normal View History

2020-03-08 15:05:20 +13:00
defmodule AugieWeb.SparklineComponent do
use Phoenix.LiveComponent
import Augie.Utils
@default_width 325
@default_height 24
@default_pad 0
def render(assigns) do
~L"""
<svg width="<%= width_from(@socket) %>" height="<%= height_from(@socket) %>" viewBox="0 0 <%= width_from(@socket) %> <%= height_from(@socket) %>" class="sparkline">
<%= if fill?(@socket) do %>
<polygon points="
<%= for {datum, index} <- Enum.with_index(padded_data(@socket)) do %>
<%= if index == 0 do %>
<%= scale_x(index, @socket) %>,<%= height_from(@socket) %>
<% end %>
<%= scale_x(index, @socket) %>,<%= scale_y(datum, @socket) %>
<% end %>
<%= width_from(@socket) %>,<%= height_from(@socket) %>
" class="sparkline--filled-area" />
<% end %>
<path d="
<%= for {datum, index} <- Enum.with_index(padded_data(@socket)) do %>
<%= if index == 0 do %>
M <%= scale_x(index, @socket) %> <%= scale_y(datum, @socket) %>
<% else %>
L <%= scale_x(index, @socket) %> <%= scale_y(datum, @socket) %>
<% end %>
<% end %>
" class="sparkline--line">
</svg>
"""
end
defp fill?(%{assigns: %{fill: false}}), do: false
defp fill?(socket) do
Enum.count(data(socket)) > 1
end
defp width_from(%{assigns: %{width: value}}), do: value
defp width_from(_socket), do: @default_width
defp height_from(%{assigns: %{height: value}}), do: value
defp height_from(_socket), do: @default_height
defp sample_count(%{assigns: %{sample_count: value}}), do: value
defp sample_count(%{assigns: %{data: value}}), do: Enum.count(value)
defp pad_with(%{assigns: %{pad_with: value}}), do: value
defp pad_with(_socket), do: @default_pad
defp data(%{assigns: %{data: value}}), do: value
defp data(_socket), do: []
defp padded_data(socket) do
data = data(socket)
count = sample_count(socket)
pad_with = pad_with(socket)
pad(data, count, pad_with)
end
defp min_from(%{assigns: %{min: value}}), do: value
defp min_from(%{assigns: %{data: data}}), do: Enum.min(data)
defp max_from(%{assigns: %{max: value}}), do: value
defp max_from(%{assigns: %{data: data}}), do: Enum.max(data)
defp safe_min(i, min) when i >= min, do: i
defp safe_min(i, min) when i < min, do: min
defp scale_x(index, socket) do
width = width_from(socket)
count = sample_count(socket)
sample_width = width / safe_min(count - 1, 1)
sample_width * index
end
defp scale_y(datum, socket) do
min = min_from(socket)
max = max_from(socket)
scale_y(datum, socket, min, max)
end
defp scale_y(datum, socket, min, max) when min < 0 do
abs_min = abs(min)
scale_y(datum + abs_min, socket, 0, max + abs_min)
end
defp scale_y(datum, socket, min, max) when max - min <= 0 do
scale_y(datum, socket, 0, 1)
end
defp scale_y(datum, socket, min, max) do
height = height_from(socket)
range = max - min
per_pixel = height / range
# IO.inspect(%{height: height, range: range, per_pixel: per_pixel})
height - per_pixel * datum
end
end