io.data2viz.shape.stack.StackOrder.kt Maven / Gradle / Ivy
package io.data2viz.shape.stack
enum class StackOrder {
/**
* Returns the given series order [0, 1, … n - 1] where n is the number of elements in series.
* Thus, the stack order is given by the key accessor.
*/
NONE,
/**
* Returns a series order such that the smallest series (according to the sum of values) is at the bottom.
*/
ASCENDING,
/**
* Returns a series order such that the largest series (according to the sum of values) is at the bottom.
*/
DESCENDING,
/**
* Returns the reverse of the given series order [n - 1, n - 2, … 0] where n is the number of elements in series.
* Thus, the stack order is given by the reverse of the key accessor.
*/
REVERSE,
/**
* Returns a series order such that the larger series (according to the sum of values) are on the inside and
* the smaller series are on the outside.
* This order is recommended for streamgraphs in conjunction with the wiggle offset.
* See [Stacked Graphs—Geometry & Aesthetics by Byron & Wattenberg for more information.](http://leebyron.com/streamgraph/)
*/
INSIDEOUT;
internal fun sort(stackParams: List>): List =
when (this) {
ASCENDING -> stackParams.sortAscending()
DESCENDING -> stackParams.sortDescending()
REVERSE -> stackParams.sortReverse()
INSIDEOUT -> stackParams.sortInsideOut()
NONE -> stackParams.sortNone()
}
}
private fun List>.sortInsideOut(): List {
val ascendingIndexes = this.sortDescending()
var topSum = .0
var bottomSum = .0
val top = mutableListOf()
val bottom = mutableListOf()
ascendingIndexes.forEach { index ->
val stackParam = this.get(index)
if (topSum < bottomSum) {
top.add(stackParam.index)
topSum += stackParam.stackedValues.sumByDouble { it.to }
} else {
bottom.add(stackParam.index)
bottomSum += stackParam.stackedValues.sumByDouble { it.to }
}
}
return bottom.reversed() + top
}
private fun List>.sortAscending() =
sumSeries()
.sortedBy { it.sum }
.map { it.index }
private fun List>.sortDescending() =
sumSeries()
.sortedByDescending { it.sum }
.map { it.index }
private fun List>.sortNone() =
sumSeries()
.map { it.index }
private fun List>.sortReverse(): List =
sumSeries()
.map { it.index }
.reversed()
internal fun List>.sumSeries(): List =
map { serie ->
SeriesSum(serie.index, serie.stackedValues.sumByDouble { it.to - it.from })
}
internal data class SeriesSum(
val index: Int,
val sum: Double
)