| | import type { LayoutRect } from './treemap-layout' |
| |
|
| | export function layoutTreemap(sizes: number[], rect: LayoutRect): LayoutRect[] { |
| | if (sizes.length === 0) return [] |
| | if (sizes.length === 1) return [rect] |
| |
|
| | const totalSize = sizes.reduce((a, b) => a + b, 0) |
| | const normalizedSizes = sizes.map( |
| | (s) => (s / totalSize) * rect.width * rect.height |
| | ) |
| |
|
| | const result: LayoutRect[] = [] |
| | let remaining = [...normalizedSizes] |
| | let currentRect = { ...rect } |
| | let totalRemaining = remaining.reduce((a, b) => a + b, 0) |
| |
|
| | while (remaining.length > 1) { |
| | |
| | const vertical = currentRect.width >= currentRect.height |
| |
|
| | |
| | const picked: number[] = [] |
| | let sum = 0 |
| |
|
| | for (const size of remaining) { |
| | picked.push(size) |
| | sum += size |
| |
|
| | if (vertical) { |
| | const width = (currentRect.width * sum) / totalRemaining |
| | if (width > (currentRect.height / picked.length) * 0.9) { |
| | break |
| | } |
| | } else { |
| | const height = (currentRect.height * sum) / totalRemaining |
| | if (height > (currentRect.width / picked.length) * 0.9) { |
| | break |
| | } |
| | } |
| | } |
| |
|
| | |
| | if (picked.length === 0) { |
| | picked.push(remaining[0]) |
| | sum = remaining[0] |
| | } |
| |
|
| | |
| | const spaceRatio = sum / totalRemaining |
| |
|
| | totalRemaining -= sum |
| |
|
| | if (vertical) { |
| | |
| | const rowWidth = Math.round(spaceRatio * currentRect.width) |
| | let offsetY = 0 |
| |
|
| | for (let i = 0; i < picked.length; i++) { |
| | const size = picked[i] |
| | const itemHeight = |
| | i === picked.length - 1 |
| | ? Math.round(currentRect.height - offsetY) |
| | : Math.round((size / sum) * currentRect.height) |
| |
|
| | result.push({ |
| | x: Math.round(currentRect.x), |
| | y: Math.round(currentRect.y + offsetY), |
| | width: rowWidth, |
| | height: itemHeight, |
| | }) |
| | offsetY += itemHeight |
| | } |
| |
|
| | |
| | currentRect = { |
| | x: Math.round(currentRect.x + rowWidth), |
| | y: Math.round(currentRect.y), |
| | width: Math.round(currentRect.width - rowWidth), |
| | height: Math.round(currentRect.height), |
| | } |
| | } else { |
| | |
| | const rowHeight = Math.round(spaceRatio * currentRect.height) |
| | let offsetX = 0 |
| |
|
| | for (let i = 0; i < picked.length; i++) { |
| | const size = picked[i] |
| | const itemWidth = |
| | i === picked.length - 1 |
| | ? Math.round(currentRect.width - offsetX) |
| | : Math.round((size / sum) * currentRect.width) |
| |
|
| | result.push({ |
| | x: Math.round(currentRect.x + offsetX), |
| | y: Math.round(currentRect.y), |
| | width: itemWidth, |
| | height: rowHeight, |
| | }) |
| | offsetX += itemWidth |
| | } |
| |
|
| | |
| | currentRect = { |
| | x: Math.round(currentRect.x), |
| | y: Math.round(currentRect.y + rowHeight), |
| | width: Math.round(currentRect.width), |
| | height: Math.round(currentRect.height - rowHeight), |
| | } |
| | } |
| |
|
| | |
| | remaining = remaining.slice(picked.length) |
| | } |
| |
|
| | |
| | if (remaining.length === 1) { |
| | result.push(currentRect) |
| | } |
| |
|
| | return result |
| | } |
| |
|