defmodule Bloccs.Web.Components.Chart do
@moduledoc """
Server-rendered SVG charts for the Messages panel — no client charting library.
`throughput/1` is an events-per-second volume chart (one bar per 1s bucket,
failures stacked in red, à la Sentry); `sparkline/1` is a tiny inline line.
The series is computed by `Bloccs.Web.Telemetry.Flow` and redrawn each frame.
"""
use Phoenix.Component
@w 720
@h 80
@pad_top 14
@pad_bottom 1
attr :series, :list, required: true
def throughput(assigns) do
series = assigns.series
max = series |> Enum.map(& &1.total) |> Enum.max(fn -> 0 end) |> max(1)
n = max(length(series), 1)
bw = @w / n
plot = @h - @pad_top - @pad_bottom
bars =
series
|> Enum.with_index()
|> Enum.map(fn {b, i} ->
total_h = b.total / max * plot
fail_h = (b[:failed] || 0) / max * plot
%{
x: Float.round(i * bw + bw * 0.12, 2),
w: Float.round(bw * 0.76, 2),
total_y: Float.round(@h - @pad_bottom - total_h, 2),
total_h: Float.round(total_h, 2),
fail_y: Float.round(@h - @pad_bottom - fail_h, 2),
fail_h: Float.round(fail_h, 2),
total: b.total,
failed: b[:failed] || 0
}
end)
assigns =
assign(assigns,
w: @w,
h: @h,
max: max,
bars: bars,
empty: max <= 1 and total_of(series) == 0
)
~H"""
"""
end
defp total_of(series), do: series |> Enum.map(& &1.total) |> Enum.sum()
@sw 84
@sh 22
attr :values, :list, required: true
@doc "A tiny inline sparkline for a list of numbers (per-node throughput, etc.)."
def sparkline(assigns) do
values = assigns.values || []
max = values |> Enum.max(fn -> 0 end) |> max(1)
n = max(length(values), 1)
pts =
values
|> Enum.with_index()
|> Enum.map_join(" ", fn {v, i} ->
sx = if n == 1, do: @sw, else: Float.round(i / (n - 1) * @sw, 1)
sy = Float.round(@sh - 1 - v / max * (@sh - 2), 1)
"#{sx},#{sy}"
end)
assigns =
assign(assigns, sw: @sw, sh: @sh, pts: pts, empty: values == [] or Enum.sum(values) == 0)
~H"""
"""
end
end