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

io.data2viz.hierarchy.TreemapLayout.kt Maven / Gradle / Ivy

There is a newer version: 0.8.0-RC5
Show newest version
package io.data2viz.hierarchy

import io.data2viz.hierarchy.treemap.treemapSquarify
import kotlin.math.roundToInt

data class Row(
    override var value: Double?,
    val dice:Boolean,
    override val children:List>
) : ParentValued>

class TreemapNode(
    val data: D,
    var depth: Int,
    var height: Int,
    override var value: Double?,                 // TODO differentiate value and SUM
    override val children: MutableList> = mutableListOf(),
    override var parent: TreemapNode? = null,
    var x0: Double = .0,
    var y0: Double = .0,
    var x1: Double = .0,
    var y1: Double = .0
): ParentValued>, Children>

internal fun roundNode(node: TreemapNode<*>) {
    node.x0 = node.x0.roundToInt().toDouble()
    node.y0 = node.y0.roundToInt().toDouble()
    node.x1 = node.x1.roundToInt().toDouble()
    node.y1 = node.y1.roundToInt().toDouble()
}

internal fun  makeTreemap(root: Node): TreemapNode {
    val rootTreemap = TreemapNode(root.data, root.depth, root.height, root.value)
    val nodes = mutableListOf(root)
    val nodesTM = mutableListOf(rootTreemap)
    while (nodes.isNotEmpty()) {
        val node = nodes.removeAt(nodes.lastIndex)
        val nodeTM = nodesTM.removeAt(nodesTM.lastIndex)
        node.children.forEach { child ->
            val c = TreemapNode(child.data, child.depth, child.height, child.value)
            c.parent = nodeTM
            nodeTM.children.add(c)
            nodes.add(child)
            nodesTM.add(c)
        }
    }
    return rootTreemap
}

class TreemapLayout {

    private val constantZero: (TreemapNode) -> Double = { .0 }

    var tilingMethod: (ParentValued>, Double, Double, Double, Double) -> Any = {
            parent: ParentValued>, x0: Double, y0: Double, x1: Double, y1: Double -> treemapSquarify(parent, x0, y0, x1, y1)
    }
    var round = false
    var width = 1.0
    var height = 1.0

    private var paddingStack = mutableListOf(.0)
    var paddingInner: (TreemapNode) -> Double = constantZero
    var paddingTop: (TreemapNode) -> Double = constantZero
    var paddingRight: (TreemapNode) -> Double = constantZero
    var paddingBottom: (TreemapNode) -> Double = constantZero
    var paddingLeft: (TreemapNode) -> Double = constantZero
    var paddingOuter: (TreemapNode) -> Double = constantZero
        set(value) {
            paddingTop = value
            paddingRight = value
            paddingBottom = value
            paddingLeft = value
        }

    /**
     * Introduced by Ben Shneiderman in 1991, a treemap recursively subdivides area into rectangles according to
     * each node’s associated value.
     * Treemap implementation supports an extensible tiling method: the default squarified method seeks to generate
     * rectangles with a golden aspect ratio; this offers better readability and size estimation than slice-and-dice,
     * which simply alternates between horizontal and vertical subdivision by depth.
     *
     * Lays out the specified root hierarchy, assigning the following properties on root and its descendants:
     *
     * - node.x0 - the left edge of the rectangle
     * - node.y0 - the top edge of the rectangle
     * - node.x1 - the right edge of the rectangle
     * - node.y1 - the bottom edge of the rectangle
     *
     * You must call root.sum before passing the hierarchy to the treemap layout so each node as a positive value.
     * // TODO force a call on root.sum ?
     * You probably also want to call root.sort to order the hierarchy before computing the layout.
     */
    fun treemap(root: Node): TreemapNode {

        // TODO : require a check on each node to verify that value != null and >0 ? (root.sum has been passed) ?

        val rootTreemap = makeTreemap(root)

        paddingStack = MutableList(root.height + 1, { .0 })
        rootTreemap.x0 = .0
        rootTreemap.y0 = .0
        rootTreemap.x1 = width
        rootTreemap.y1 = height

        rootTreemap.eachBefore(this::positionNode)
        paddingStack = mutableListOf(.0)

        if (round) rootTreemap.eachBefore(::roundNode)

        return rootTreemap
    }

    private fun positionNode(node: TreemapNode) {
        var p = paddingStack[node.depth]
        var x0 = node.x0 + p
        var y0 = node.y0 + p
        var x1 = node.x1 - p
        var y1 = node.y1 - p

        if (x1 < x0) {
            val mid = (x0 + x1) / 2
            x0 = mid
            x1 = mid
        }
        if (y1 < y0) {
            val mid = (y0 + y1) / 2
            y0 = mid
            y1 = mid
        }
        node.x0 = x0
        node.y0 = y0
        node.x1 = x1
        node.y1 = y1

        if (node.children.isNotEmpty()) {
            paddingStack[node.depth + 1] = paddingInner(node) / 2
            p = paddingInner(node) / 2
            x0 += paddingLeft(node) - p
            y0 += paddingTop(node) - p
            x1 -= paddingRight(node) - p
            y1 -= paddingBottom(node) - p
            if (x1 < x0) {
                val mid = (x0 + x1) / 2
                x0 = mid
                x1 = mid
            }
            if (y1 < y0) {
                val mid = (y0 + y1) / 2
                y0 = mid
                y1 = mid
            }
            tilingMethod(node, x0, y0, x1, y1)
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy