ML-200: switch to a vertical chart

This commit is contained in:
Claudio Ortolina
2026-05-31 22:02:12 +03:00
parent e67b172d64
commit e64f2b12c4
4 changed files with 142 additions and 13 deletions
@@ -75,6 +75,77 @@ defmodule MusicLibraryWeb.ChartComponents do
"""
end
@doc """
Renders a vertical bar chart using CSS Grid.
Columns flow horizontally, use a fixed-height plotting area, and scroll on
narrow screens while keeping labels and values visible.
## Examples
<.vertical_bar_chart
data={[{"May 01", 5}, {"May 02", 3}]}
label_fn={&elem(&1, 0)}
value_fn={&elem(&1, 1)}
color_class="bg-emerald-500 dark:bg-emerald-400"
/>
"""
attr :data, :list, required: true
attr :label_fn, :any, required: true
attr :value_fn, :any, required: true
attr :color_class, :string, required: true
attr :class, :string, default: ""
attr :datum_click, :any, default: nil, doc: "the function for handling phx-click on each datum"
def vertical_bar_chart(assigns) when assigns.data != [] do
assigns =
assigns
|> assign(:max_value, max_value(assigns.data, assigns.value_fn))
~H"""
<div class={["w-full overflow-x-auto p-4", @class]}>
<div class="grid min-w-full grid-flow-col auto-cols-[minmax(1.75rem,1fr)] items-end gap-2">
<%= for datum <- @data do %>
<% percentage = bar_percentage(@value_fn.(datum), @max_value) %>
<% label = to_string(@label_fn.(datum)) %>
<%= if @datum_click do %>
<div
class="group flex h-72 cursor-pointer flex-col items-center justify-end gap-2"
phx-click={@datum_click.(datum)}
>
<.vertical_bar_column
label={label}
percentage={percentage}
value={@value_fn.(datum)}
color_class={@color_class}
grouped
/>
</div>
<% else %>
<div class="flex h-72 flex-col items-center justify-end gap-2">
<.vertical_bar_column
label={label}
percentage={percentage}
value={@value_fn.(datum)}
color_class={@color_class}
/>
</div>
<% end %>
<% end %>
</div>
</div>
"""
end
def vertical_bar_chart(assigns) do
~H"""
<div class={["w-full", @class]}>
{gettext("No data available")}
</div>
"""
end
attr :label, :string, required: true
attr :percentage, :float, required: true
attr :value, :any, required: true
@@ -110,6 +181,49 @@ defmodule MusicLibraryWeb.ChartComponents do
"""
end
attr :label, :string, required: true
attr :percentage, :float, required: true
attr :value, :any, required: true
attr :color_class, :string, required: true
attr :grouped, :boolean, default: false
defp vertical_bar_column(assigns) do
~H"""
<div class="shrink-0 text-xs font-semibold text-zinc-500 dark:text-zinc-400">
{@value}
</div>
<div
class="flex h-36 w-full shrink-0 items-end rounded bg-zinc-200 dark:bg-zinc-700"
title={"#{@label}: #{@value}"}
aria-label={"#{@label}: #{@value}"}
data-chart-label={@label}
data-chart-value={@value}
>
<div
class={[
"w-full rounded-t opacity-80 transition-opacity",
@color_class,
if(@grouped, do: "group-hover:opacity-100")
]}
style={"height: #{@percentage}%"}
>
</div>
</div>
<div class="relative h-20 w-full shrink-0 overflow-visible">
<div
class={[
"absolute top-6 left-1/2 w-16 origin-center -translate-x-1/2 -rotate-90 text-center text-xs font-medium whitespace-nowrap",
"text-zinc-500 dark:text-zinc-400",
if(@grouped, do: "group-hover:text-zinc-700 dark:group-hover:text-zinc-200")
]}
title={@label}
>
{@label}
</div>
</div>
"""
end
defp max_value(data, value_fn) do
max = Enum.max_by(data, value_fn)
value_fn.(max)