102 lines
3.1 KiB
Elixir
102 lines
3.1 KiB
Elixir
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
|