All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.treemap.Squarify.kt Maven / Gradle / Ivy

package com.juul.krayon.hierarchy.treemap

import com.juul.krayon.hierarchy.Node

private const val GOLDEN_RATIO = 1.61803398875f

/** Implementation of https://www.win.tue.nl/~vanwijk/stm.pdf */
public class Squarify(
    private val aspectRatio: Float = GOLDEN_RATIO,
) : TileMethod {

    init {
        require(aspectRatio >= 1f) { "Aspect ratio cannot be less than 1, but was `$aspectRatio`." }
    }

    override fun tile(parent: Node<*, Tile>) {
        var i0 = 0
        var i1 = 0
        var remainingTile = parent.layout
        var remainingWeight = parent.weight
        while (i0 < parent.children.size) {
            val width = remainingTile.width
            val height = remainingTile.height

            // Find a non-empty node
            var sumWeight: Float
            do {
                sumWeight = parent.children[i1++].weight
            } while (sumWeight == 0f && i1 < parent.children.size)

            // Calculate the initial state from that node
            var minWeight = sumWeight
            var maxWeight = sumWeight
            val alpha = maxOf(height / width, width / height) / (remainingWeight * aspectRatio)
            var beta = sumWeight * sumWeight * alpha
            var minRatio = maxOf(maxWeight / beta, beta / minWeight)

            // Keep checking nodes until the fit is worse
            while (i1 < parent.children.size) {
                val nodeWeight = parent.children[i1].weight
                sumWeight += nodeWeight
                if (nodeWeight < minWeight) minWeight = nodeWeight
                if (nodeWeight > maxWeight) maxWeight = nodeWeight
                beta = sumWeight * sumWeight * alpha
                val newRatio = maxOf(maxWeight / beta, beta / minWeight)
                if (newRatio > minRatio) {
                    sumWeight -= nodeWeight
                    break
                } else {
                    minRatio = newRatio
                    i1 += 1
                }
            }

            // Position the nodes
            val fillTile = if (width < height) {
                remainingTile.copy(bottom = remainingTile.top + height * (sumWeight / remainingWeight))
            } else {
                remainingTile.copy(right = remainingTile.left + width * (sumWeight / remainingWeight))
            }
            val dummy = Node(parent.data, fillTile).also {
                it.weight = sumWeight
                it.children = parent.children.subList(i0, i1) as List>
            }
            val method = if (width < height) Dice else Slice
            method.tile(dummy)
            remainingWeight -= sumWeight
            remainingTile = if (width < height) {
                remainingTile.copy(top = fillTile.bottom)
            } else {
                remainingTile.copy(left = fillTile.right)
            }
            i0 = i1
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy