defmodule MusicLibraryWeb.ChartComponents do use MusicLibraryWeb, :live_component @doc """ Renders a horizontal bar chart. ## Examples <.vertical_bar_chart data={[{"Artist 1", 5}, {"Artist 2", 3}]} label_fn={&elem(&1, 0)} value_fn={&elem(&1, 1)} width={400} height={300} color_class="fill-red-500" /> """ attr :data, :list, required: true attr :label_fn, :any, required: true attr :value_fn, :any, required: true attr :width, :integer, default: 400 attr :height, :integer, default: 300 attr :color_class, :string, required: true attr :class, :string, default: "" attr :label_click, :any, default: nil, doc: "the function for handling phx-click on each label" def vertical_bar_chart(assigns) do assigns = assigns |> assign(:padding, 40) |> assign(:max_value, max_value(assigns.data, assigns.value_fn)) # Account for labels and padding |> assign(:chart_width, assigns.width - 150 - 40) # Account for bottom padding |> assign(:chart_height, assigns.height - 40) |> assign(:bar_height, calculate_bar_height(assigns.data, assigns.height - 40)) ~H"""
<%!-- Bars and labels --%> <%= for {datum, index} <- Enum.with_index(@data) do %> <% bar_width = @chart_width * @value_fn.(datum) / @max_value %> <% y = @padding / 2 + index * (@bar_height + 4) %> <%!-- Label --%> {truncate_label(@label_fn.(datum), 20)} <%!-- Bar --%> {@label_fn.(datum)}: {@value_fn.(datum)} <%!-- Value label --%> {@value_fn.(datum)} <% end %>
""" end defp max_value(data, value_fn) do max = Enum.max_by(data, value_fn) value_fn.(max) end defp calculate_bar_height(data, available_height) do bar_count = length(data) max_height = min(30, (available_height - (bar_count - 1) * 4) / bar_count) max(20, max_height) |> trunc() end defp truncate_label(label, max_length) when byte_size(label) > max_length do String.slice(label, 0, max_length - 2) <> ".." end defp truncate_label(label, _), do: label end