52 行
1.9 KiB
Ruby
52 行
1.9 KiB
Ruby
# Coordinates carry their aggregation depth; SQL NULL remains an ordinary axis value.
|
|
class CountsPivotLayout
|
|
def self.depths(keys, sorts, measures: true)
|
|
return [] if keys.length < 2 || sorts.first&.first == 'count' ||
|
|
sorts.first&.first&.match?(/\Am[0-9]+\z/)
|
|
|
|
relevant = sorts.map(&:first).select { keys.include?(_1) || (measures && (_1 == 'count' ||
|
|
_1.match?(/\Am[0-9]+\z/))) }
|
|
order = (relevant + keys).uniq
|
|
(1...keys.length).select { order.first(_1).sort == keys.first(_1).sort }
|
|
end
|
|
|
|
def self.label_key(row_count, column_count, row_depth, column_depth)
|
|
total = row_count + column_count
|
|
collapsed = total - row_depth - column_depth
|
|
return nil if collapsed.zero?
|
|
return 'grandTotal' if total >= 3 && collapsed == total
|
|
if (collapsed == 1 &&
|
|
((row_count > 1 && row_depth < row_count) ||
|
|
(column_count > 1 && column_depth < column_count)))
|
|
return 'subtotal'
|
|
end
|
|
'singleTotal'
|
|
end
|
|
|
|
def self.headers(values, depths, next_values: nil, grand: false, axis_count: nil,
|
|
other_count: 0, direction: :row)
|
|
result = []
|
|
values.each_with_index do |tuple, index|
|
|
result << { values: tuple, kind: 'data' }
|
|
following = values[index + 1] || next_values
|
|
depths.reverse_each do |depth|
|
|
prefix = tuple.first(depth)
|
|
next if following && following.first(depth) == prefix
|
|
|
|
result << { values: prefix, kind: 'subtotal' }
|
|
end
|
|
end
|
|
result << { values: [], kind: 'grand_total' } if grand
|
|
count = axis_count || values.first&.length || 0
|
|
result.each do |header|
|
|
depth = header[:values].length
|
|
label = if header[:kind] != 'data'
|
|
direction == :row ? label_key(count, other_count, depth, other_count) :
|
|
label_key(other_count, count, other_count, depth)
|
|
end
|
|
header.merge!(depth:, axis_count: count, label_key: label)
|
|
end
|
|
result
|
|
end
|
|
end
|