I think it cleaned up nicely already. Must say: has been very instructive exercise.
defmodule SurveyWeb.Structs.Bins do
defstruct [:size, :start, :count]
def index_of(value, bins) when value >= bins.start + bins.size * bins.count do
bins.count - 1
end
def index_of(value, bins) do
(value - bins.start)/bins.size
|> trunc()
end
def index_to_label(index, bins) when index == bins.count - 1 do
lower_limit = bins.start + bins.size * index
"#{lower_limit}+"
end
def index_to_label(index, bins) do
lower_limit = bins.start + bins.size * index
upper_limit = lower_limit + bins.size - 1
"#{lower_limit}-#{upper_limit}"
end
end
defmodule SurveyWeb.Components.Histogram do
use SurveyWeb, :live_component
alias SurveyWeb.Components.Helpers
alias SurveyWeb.Structs.Bins
defp histogram_frequencies(responses, target_variable, bin_size, start_value, bin_count) do
bins = %Bins{size: bin_size, start: start_value, count: bin_count}
responses
|> Enum.frequencies_by(fn %{^target_variable => value } -> Bins.index_of(value, bins) end)
|> add_empty_bins(bins)
|> List.first() # Temp. Getting rid of this line.
|> Enum.map(fn {index, count} -> {Bins.index_to_label(index, bins), count} end)
end
defp add_empty_bins(frequencies, bins) do
missing_indexes = Enum.to_list(0..bins.count - 1) -- Map.keys(frequencies)
for index <- missing_indexes do
Map.put(frequencies, index, 0)
end
end
def render(assigns) do
~H"""
<span class={"#{@class} histogram"}>
<%= for {category, count} <-
histogram_frequencies(@responses, @target_variable, @bin_size, @start_value, @bin_count) do %>
<div class="bar" style={"height: #{count/Enum.count(@responses)*100}%"}>
<div class="count">
<%= count %>
<%= "(#{Helpers.decimal_responses_percentage(@responses, count, 1)})" %>
</div>
<div class="category">
<%= category %>
</div>
</div>
<% end %>
</span>
"""
end
end






















