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

commonMain.Data.kt Maven / Gradle / Ivy

The newest version!
package com.juul.krayon.selection

import com.juul.krayon.element.Element

/** See analogous [d3 function](https://github.com/d3/d3-selection#selection_data). */
public fun  Selection.data(
    value: List,
): UpdateSelection = data { _, _ -> value }

/** See analogous [d3 function](https://github.com/d3/d3-selection#selection_data). */
public fun  Selection.data(
    value: (index: Int, group: Group) -> List,
): UpdateSelection = dataImpl(value, null)

/**
 * See analogous [d3 function](https://github.com/d3/d3-selection#selection_data).
 *
 * TODO: Figure out a way to strongly type [key], even though it's called with
 *       two different sets of argument types.
 */
public fun  Selection.keyedData(
    value: List,
    key: Element?.(Arguments) -> Any?,
): UpdateSelection =
    keyedData(value = { _, _ -> value }, key)

/**
 * See analogous [d3 function](https://github.com/d3/d3-selection#selection_data).
 *
 * TODO: Figure out a way to strongly type [key], even though it's called with
 *       two different sets of argument types.
 */
public fun  Selection.keyedData(
    value: (index: Int, group: Group) -> List,
    key: Element?.(Arguments) -> Any?,
): UpdateSelection = dataImpl(value, key)

private fun  Selection.dataImpl(
    value: (index: Int, group: Group) -> List,
    key: (Element?.(Arguments) -> Any?)?,
): UpdateSelection {
    val update = ArrayList>(groups.size)
    val enter = ArrayList>(groups.size)
    val exit = ArrayList>(groups.size)

    for ((index, group) in groups.withIndex()) {
        val data = value(index, group)
        val (updateNodes, enterNodes, exitNodes) = when (key) {
            null -> bindIndex(group, data)
            else -> bindKey(group, data, key)
        }
        update += Group(group.parent, updateNodes)
        enter += Group(group.parent, enterNodes)
        exit += Group(group.parent, exitNodes)

        // Associate enter elements with their next update element
        var updateIndex = 0
        for ((enterIndex, node) in enterNodes.withIndex()) {
            if (node != null) {
                if (enterIndex >= updateIndex) {
                    updateIndex = enterIndex + 1
                }
                node.next = updateNodes.subList(updateIndex, updateNodes.size)
                    .onEach { if (it == null) updateIndex += 1 }
                    .filterNotNull()
                    .firstOrNull()
            }
        }
    }

    return UpdateSelection(update, EnterSelection(enter), ExitSelection(exit))
}

private fun  bindIndex(
    group: Group,
    data: List,
): Triple, List, List> {
    val update = ArrayList(data.size)
    val enter = ArrayList(data.size)
    val exit = ArrayList(maxOf(data.size, group.nodes.size))
    data.forEachIndexed { index, value ->
        val node = group.nodes.getOrNull(index)
        if (node != null) {
            update.add(node.also { it.data = value })
            enter.add(null)
        } else {
            update.add(null)
            enter.add(
                EnterElement().also {
                    it.data = value
                    it.parent = group.parent
                },
            )
        }
        exit.add(null)
    }
    if (group.nodes.size > data.size) {
        group.nodes
            .subList(data.size, group.nodes.size)
            .forEach { node ->
                exit.add(node)
            }
    }
    return Triple(update, enter, exit)
}

private fun  bindKey(
    group: Group,
    data: List,
    key: Element?.(Arguments) -> Any?,
): Triple, List, List> {
    val update = ArrayList(data.size)
    val enter = ArrayList(data.size)
    val exit = ArrayList(group.nodes.size)

    val nodeByKeyValue = HashMap()
    val keys = ArrayList(group.nodes.size)

    // Compute keys for existing nodes.
    // If multiple nodes share a key, nodes after the first are added to exit.
    val arguments = Arguments.Buffer()
    group.nodes.forEachIndexed { index, node ->
        if (node == null) {
            keys.add(null)
            exit.add(null)
        } else {
            val keyValue = node.key(arguments(node.data, index, group.nodes))
            keys.add(keyValue)
            if (keyValue in nodeByKeyValue) {
                exit.add(node)
            } else {
                exit.add(null)
                nodeByKeyValue[keyValue] = node
            }
        }
    }

    // Compute keys for data.
    // Matching nodes are added to update and removed from the key map.
    // If no node matches, add it to enter.
    // If multiple datums share a key, datums after the first are added to enter.
    data.forEachIndexed { index, value ->
        val keyValue = group.parent.key(arguments(value, index, data))
        val node = nodeByKeyValue[keyValue]
        if (node == null) {
            enter.add(
                EnterElement().also {
                    it.parent = group.parent
                    it.data = value
                },
            )
            update.add(null)
            nodeByKeyValue.remove(keyValue)
        } else {
            enter.add(null)
            update.add(
                node.also {
                    it.data = value
                },
            )
        }
    }

    // Nodes that still exist in the key map are inserted into exit.
    group.nodes.forEachIndexed { index, node ->
        if (node != null && nodeByKeyValue[keys[index]] === node) {
            exit[index] = node
        }
    }

    return Triple(update, enter, exit)
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy