defmodule MusicLibraryWeb.ChartComponents do use Phoenix.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} bar_color="rgb(79, 70, 229)" /> """ 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 :bar_color, :string, default: "rgb(79, 70, 229)" 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 |> assign(:chart_width, assigns.width - 150) # Account for bottom padding |> assign(:chart_height, assigns.height - 40) |> assign(:bar_height, calculate_bar_height(assigns.data, assigns.height - 40)) ~H""" <%!-- X-axis labels and lines --%> <%= for {value, x} <- x_axis_values(@max_value, @chart_width) do %> {value} <% end %> <%!-- 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 x_axis_values(max_value, width) do step = max_value / 5 0..5 |> Enum.map(fn i -> value = i * step x = i * width / 5 {trunc(value), trunc(x)} end) 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