diff --git a/lib/music_library/records.ex b/lib/music_library/records.ex index 02745507..4f05cc69 100644 --- a/lib/music_library/records.ex +++ b/lib/music_library/records.ex @@ -267,4 +267,38 @@ defmodule MusicLibrary.Records do def change_record(%Record{} = record, attrs \\ %{}) do Record.changeset(record, attrs) end + + @doc """ + Returns a list of tuples containing artist names and their record count, + ordered by count in descending order. + """ + def count_records_by_artist do + q = + from r in fragment(""" + select json_extract(artist.value, '$.name') as name, count(1) as count + from records, json_each(records.artists) artist + group by name + order by count desc + """), + select: {fragment("name"), fragment("count")} + + Repo.all(q) + end + + @doc """ + Returns a list of tuples containing genre names and their record count, + ordered by count in descending order. + """ + def count_records_by_genre do + q = + from r in fragment(""" + select genre.value as name, count(1) as count + from records, json_each(records.genres) genre + group by name + order by count desc + """), + select: {fragment("name"), fragment("count")} + + Repo.all(q) + end end diff --git a/lib/music_library_web/components/chart_components.ex b/lib/music_library_web/components/chart_components.ex new file mode 100644 index 00000000..c6bffe92 --- /dev/null +++ b/lib/music_library_web/components/chart_components.ex @@ -0,0 +1,122 @@ +defmodule MusicLibraryWeb.ChartComponents do + use Phoenix.Component + + @doc """ + Renders a horizontal bar chart. + + ## Examples + <.vertical_bar_chart + data={[{"Artist 1", 5}, {"Artist 2", 3}]} + width={400} + height={300} + bar_color="rgb(79, 70, 229)" + /> + """ + attr :data, :list, required: true, doc: "List of {label, value} tuples" + attr :width, :integer, default: 400 + attr :height, :integer, default: 300 + attr :bar_color, :string, default: "rgb(79, 70, 229)" + attr :class, :string, default: "" + + def vertical_bar_chart(assigns) do + assigns = + assigns + |> assign(:padding, 40) + |> assign(:max_value, max_value(assigns.data)) + # 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""" + + """ + end + + defp max_value(data) do + {_, max} = Enum.max_by(data, fn {_, value} -> value end) + 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 diff --git a/lib/music_library_web/live/stats_live/index.ex b/lib/music_library_web/live/stats_live/index.ex index 64d92166..d3b72030 100644 --- a/lib/music_library_web/live/stats_live/index.ex +++ b/lib/music_library_web/live/stats_live/index.ex @@ -3,6 +3,7 @@ defmodule MusicLibraryWeb.StatsLive.Index do import MusicLibraryWeb.DataComponents import MusicLibraryWeb.RecordComponents, only: [format_label: 1, type_label: 1] + import MusicLibraryWeb.ChartComponents alias MusicLibrary.{Artists, Collection, Records, Wishlist} @@ -28,7 +29,9 @@ defmodule MusicLibraryWeb.StatsLive.Index do scrobble_activity_mode: :albums, latest_record: latest_record, page_title: gettext("Stats"), - nav_section: :stats + nav_section: :stats, + records_by_artist: Records.count_records_by_artist(), + records_by_genre: Records.count_records_by_genre() )} end diff --git a/lib/music_library_web/live/stats_live/index.html.heex b/lib/music_library_web/live/stats_live/index.html.heex index 464db744..3858f1e6 100644 --- a/lib/music_library_web/live/stats_live/index.html.heex +++ b/lib/music_library_web/live/stats_live/index.html.heex @@ -68,6 +68,38 @@ +