defmodule Archeometer.Analysis.Treemap.Rectangle do
@moduledoc """
This module provides functions to calculate coordinates and size from
`Archeometer.Analysis.Treemap.Node` to be rendered by
`Archeometer.Analysis.Treemap.SVGrender`.
"""
defstruct coords: {0, 0},
size: {0, 0},
drawable_area: {0, 0},
start_drawable: {0, 0},
areas: []
defmodule Area do
@moduledoc """
Struct that contains starting coordinates, size and other data for an area
that will be rendered by Archeometer.Analysis.Treemap.SVGRender.
"""
defstruct [:label, :pct, :begin, :size, :value]
end
def create_rectangle(size, coords) do
%__MODULE__{size: size, drawable_area: size, coords: coords, start_drawable: coords}
end
def layout_row(%{drawable_area: {drawable_w, drawable_h}} = rectangle, row, orientation) do
weights_list = Enum.map(row, & &1.pct)
{areas, side} =
case orientation do
:hor ->
row_height = Enum.sum(weights_list) / drawable_w
areas =
calc_areas(
rectangle.start_drawable,
{drawable_w, row_height},
row,
orientation
)
{areas, row_height}
:vert ->
row_width = Enum.sum(weights_list) / drawable_h
areas =
calc_areas(
rectangle.start_drawable,
{row_width, drawable_h},
row,
orientation
)
{areas, row_width}
end
update_drawable_area(rectangle, side, orientation)
|> Map.update!(:areas, &(areas ++ &1))
end
def row_orientation(%{drawable_area: {d_w, d_h}}) do
if d_w < d_h do
:hor
else
:vert
end
end
def shortest_drawable_side(%{drawable_area: {d_w, d_h}}), do: min(d_w, d_h)
defp update_drawable_area(rectangle, row_w, :vert) do
Map.update!(rectangle, :start_drawable, fn {start_x, start_y} ->
{start_x + row_w, start_y}
end)
|> Map.update!(:drawable_area, fn {drawable_w, drawable_h} ->
{drawable_w - row_w, drawable_h}
end)
end
defp update_drawable_area(rectangle, row_h, :hor) do
Map.update!(rectangle, :start_drawable, fn {start_x, start_y} ->
{start_x, start_y + row_h}
end)
|> Map.update!(:drawable_area, fn {drawable_w, drawable_h} ->
{drawable_w, drawable_h - row_h}
end)
end
defp calc_areas(starting_point, row_dims, row, orientation) do
Enum.reduce(row, {starting_point, []}, &calc_areas_for_row(&1, &2, row_dims, orientation))
|> elem(1)
end
defp calc_areas_for_row(
node,
{{start_x, start_y}, acc},
{row_w, row_h},
orientation
) do
node_area = node.pct
{node_size, new_starting_point} =
case orientation do
:hor ->
node_w = node_area / row_h
{
{node_w, row_h},
{node_w + start_x, start_y}
}
:vert ->
node_h = node_area / row_w
{
{row_w, node_h},
{start_x, node_h + start_y}
}
end
rectangle_area = %__MODULE__.Area{
begin: {start_x, start_y},
size: node_size,
label: node.name,
pct: node.pct,
value: node.val
}
{new_starting_point, [rectangle_area | acc]}
end
end