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""" {b.total}/s{if b.failed > 0, do: " · #{b.failed} failed"} 0} class="bloccs-chart__bar" x={b.x} y={b.total_y} width={b.w} height={b.total_h} rx="1" /> 0} class="bloccs-chart__bar-fail" x={b.x} y={b.fail_y} width={b.w} height={b.fail_h} rx="1" /> peak {@max}/s waiting for traffic """ 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