io.data2viz.hierarchy.Hierarchy.kt Maven / Gradle / Ivy
package io.data2viz.hierarchy
interface Valued { var value: Double? }
interface Children { val parent: ParentValued? }
interface ParentValued: Valued {val children: List>}
data class Node(
val data: D,
var depth: Int = 0,
var height: Int = 0,
override var value: Double? = null,
override val children: MutableList> = mutableListOf(),
override var parent: Node? = null
): ParentValued>, Children>
data class Link(
val source: Node?,
var target: Node
)
/**
* Constructs a root node from the specified hierarchical data. The specified data must be an object
* representing the root node.
* The specified children accessor function is invoked for each datum, starting with the root data, and must
* return an array of data representing the children, or null if the current datum has no children.
* The specified value accessor function is invoked for each datum, starting with the root data, and must return
* a Double value representing the data. If value is not specified, it defaults to null (no value for nodes).
* TODO : value
*/
fun hierarchy(data: D, children: (D) -> List?, value: ((D) -> Double)? = null): Node {
val root = Node(data)
val nodes = mutableListOf(root)
while (nodes.size > 0) {
val node = nodes.removeAt(nodes.lastIndex)
val childs = children(node.data)
if (childs != null) {
childs.forEach { c ->
val child = Node(c)
child.parent = node
child.depth = node.depth + 1
//if (value != null) child.value = value(c)
node.children.add(child)
nodes.add(child)
}
}
}
return root.eachBefore(::computeHeight)
}
/**
* Computes the number of leaves under this node and assigns it to node.value, and similarly for every
* descendant of node.
* If this node is a leaf, its count is one.
* Returns this node.
* See also node.sum.
*/
fun Node.count(): Node {
return this.eachAfter(::nodeCount)
}
/**
* TODO check behavior (especially non-negative value)
* Evaluates the specified value function for this node and each descendant in post-order traversal,
* and returns this node.
* The node.value property of each node is set to the numeric value returned by the specified function plus
* the combined value of all descendants. The function is passed the node’s data, and must return a non-negative number.
* The value accessor is evaluated for node and every descendant, including internal nodes; if you only want leaf
* nodes to have internal value, then return zero for any node with children.
*/
fun Node.sum(value: ((D) -> Double)? = null): Node {
return this.eachAfter({ node: Node ->
var sum = if (value != null) value(node.data) else .0
node.children.forEach { child -> if (child.value != null) sum += child.value!! }
node.value = sum
})
}
/**
* Returns the array of ancestors nodes, starting with this node, then followed by each parent up to the root.
*/
fun Node.ancestors(): List> {
val nodes: MutableList> = mutableListOf(this)
var node: Node? = this
while (node != null
) {
nodes.add(node)
node = node.parent
}
return nodes.toList()
}
/**
* Returns the array of descendant nodes, starting with this node, then followed by each child in topological order.
*/
fun Node.descendants(): List> {
val nodes: MutableList> = mutableListOf()
this.each({ node: Node ->
nodes.add(node)
})
return nodes
}
/**
* Returns the array of leaf nodes in traversal order; leaves are nodes with no children.
*/
fun Node.leaves(): List> {
val leaves: MutableList> = mutableListOf()
this.eachBefore({ node: Node ->
if (node.children.isEmpty()) leaves.add(node)
})
return leaves
}
/**
* Returns an array of links for this node, where each link is an object that defines source and target properties.
* The source of each link is the parent node, and the target is a child node.
*/
fun Node.links(): List> {
val root = this
val links: MutableList> = mutableListOf()
root.each({ node: Node ->
if (node != root) links.add(Link(node.parent, node)) // Don’t include the root’s parent, if any.
})
return links.toList()
}
inline fun , D> separation(nodeA: N, nodeB: N) = if (nodeA.parent == nodeB.parent) 1 else 2
/**
* Invokes the specified function for node and each descendant in breadth-first order, such that a given
* node is only visited if all nodes of lesser depth have already been visited, as well as all preceeding
* nodes of the same depth.
* The specified function is passed the current node.
*/
inline fun , D> N.each(callback: (N) -> Unit): N {
val next = mutableListOf(this)
while (next.size > 0) {
val current = next.reversed().toMutableList()
next.clear()
val node = current.removeAt(current.lastIndex)
callback(node)
val children = node.children
if (children.isNotEmpty()) {
(children.lastIndex downTo 0).forEach {
next.add(children[it] as N)
}
}
}
return this
}
/**
* Invokes the specified function for node and each descendant in pre-order traversal, such that a given node
* is only visited after all of its ancestors have already been visited.
* The specified function is passed the current node.
*/
inline fun , D> N.eachBefore(callback: (N) -> Unit): N {
val nodes = mutableListOf(this)
while (nodes.isNotEmpty()) {
val node = nodes.removeAt(nodes.lastIndex)
callback(node)
val children = node.children
if (children.isNotEmpty()) {
(children.lastIndex downTo 0).forEach {
nodes.add(children[it] as N)
}
}
}
return this
}
/**
* Invokes the specified function for node and each descendant in post-order traversal, such that a given node
* is only visited after all of its descendants have already been visited.
* The specified function is passed the current node.
*/
inline fun , D> N.eachAfter(callback: (N) -> Unit): N {
val nodes = mutableListOf(this)
val next = mutableListOf()
while (nodes.isNotEmpty()) {
val node = nodes.removeAt(nodes.lastIndex)
next.add(node)
val children = node.children
if (children.isNotEmpty()) {
children.forEach {
nodes.add(it as N)
}
}
}
next.reversed().forEach(callback)
return this
}
private fun computeHeight(node: Node) {
var n: Node = node
var height = 0
n.height = height
height++
while (n.parent != null && n.parent!!.height < height) {
n.parent!!.height = height
n = n.parent!!
height++
}
}
private fun nodeCount(node: Node) {
var sum = .0
val children = node.children
if (children.isEmpty()) sum = 1.0
else {
children.forEach { if (it.value != null) sum += it.value!! }
}
node.value = sum
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy