commonMain.jetbrains.datalore.plot.base.geom.AreaRidgesGeom.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of lets-plot-common Show documentation
Show all versions of lets-plot-common Show documentation
Lets-Plot JVM package without rendering part
/*
* Copyright (c) 2022. JetBrains s.r.o.
* Use of this source code is governed by the MIT license that can be found in the LICENSE file.
*/
package jetbrains.datalore.plot.base.geom
import jetbrains.datalore.base.geometry.DoubleVector
import jetbrains.datalore.base.interval.DoubleSpan
import jetbrains.datalore.plot.base.*
import jetbrains.datalore.plot.base.geom.util.*
import jetbrains.datalore.plot.base.interact.GeomTargetCollector
import jetbrains.datalore.plot.base.interact.TipLayoutHint
import jetbrains.datalore.plot.base.render.SvgRoot
import jetbrains.datalore.plot.common.data.SeriesUtil
class AreaRidgesGeom : GeomBase(), WithHeight {
var scale: Double = DEF_SCALE
var minHeight: Double = DEF_MIN_HEIGHT
var quantileLines: Boolean = DEF_QUANTILE_LINES
override fun buildIntern(
root: SvgRoot,
aesthetics: Aesthetics,
pos: PositionAdjustment,
coord: CoordinateSystem,
ctx: GeomContext
) {
buildLines(root, aesthetics, pos, coord, ctx)
}
private fun buildLines(
root: SvgRoot,
aesthetics: Aesthetics,
pos: PositionAdjustment,
coord: CoordinateSystem,
ctx: GeomContext
) {
val definedDataPoints = GeomUtil.withDefined(aesthetics.dataPoints(), Aes.X, Aes.Y, Aes.HEIGHT)
if (!definedDataPoints.any()) return
definedDataPoints
.sortedByDescending(DataPointAesthetics::y)
.groupBy(DataPointAesthetics::y)
.map { (y, nonOrderedPoints) -> y to GeomUtil.ordered_X(nonOrderedPoints) }
.forEach { (_, dataPoints) ->
splitDataPointsByMinHeight(dataPoints).forEach { buildRidge(root, it, pos, coord, ctx) }
}
}
private fun splitDataPointsByMinHeight(dataPoints: Iterable): List> {
val result = mutableListOf>()
var dataPointsBunch: MutableList = mutableListOf()
for (p in dataPoints)
if (p.height()!! >= minHeight)
dataPointsBunch.add(p)
else {
if (dataPointsBunch.any()) result.add(dataPointsBunch)
dataPointsBunch = mutableListOf()
}
if (dataPointsBunch.any()) result.add(dataPointsBunch)
return result
}
private fun buildRidge(
root: SvgRoot,
dataPoints: Iterable,
pos: PositionAdjustment,
coord: CoordinateSystem,
ctx: GeomContext
) {
val helper = LinesHelper(pos, coord, ctx)
val boundTransform = toLocationBound(ctx)
dataPoints.groupBy(DataPointAesthetics::fill).forEach { (_, points) ->
val paths = helper.createBands(
points,
boundTransform,
{ p -> DoubleVector(p.x()!!, p.y()!!) },
simplifyBorders = true
)
appendNodes(paths, root)
}
helper.setAlphaEnabled(false)
dataPoints.groupBy(DataPointAesthetics::color).forEach { (_, points) ->
appendNodes(helper.createLines(points, boundTransform), root)
}
if (quantileLines) drawQuantileLines(root, dataPoints, pos, coord, ctx)
buildHints(dataPoints, ctx, helper, boundTransform)
}
private fun drawQuantileLines(
root: SvgRoot,
dataPoints: Iterable,
pos: PositionAdjustment,
coord: CoordinateSystem,
ctx: GeomContext
) {
val pIt = dataPoints.sortedWith(
compareBy(
DataPointAesthetics::group,
DataPointAesthetics::quantile,
DataPointAesthetics::x
)
).iterator()
if (!pIt.hasNext()) return
var pPrev = pIt.next()
while (pIt.hasNext()) {
val pCurr = pIt.next()
val quantilesAreSame = pPrev.quantile() == pCurr.quantile() ||
(pPrev.quantile()?.isFinite() != true && pCurr.quantile()?.isFinite() != true)
if (!quantilesAreSame) drawQuantileLine(root, pCurr, pos, coord, ctx)
pPrev = pCurr
}
}
private fun drawQuantileLine(
root: SvgRoot,
dataPoint: DataPointAesthetics,
pos: PositionAdjustment,
coord: CoordinateSystem,
ctx: GeomContext
) {
val svgElementHelper = GeomHelper(pos, coord, ctx).createSvgElementHelper()
val start = toLocationBound(ctx)(dataPoint)
val end = DoubleVector(dataPoint.x()!!, dataPoint.y()!!)
val line = svgElementHelper.createLine(start, end, dataPoint)!!
root.add(line)
}
private fun toLocationBound(ctx: GeomContext): (p: DataPointAesthetics) -> DoubleVector {
return fun(p: DataPointAesthetics): DoubleVector {
val x = p.x()!!
val y = p.y()!! + ctx.getResolution(Aes.Y) * scale * p.height()!!
return DoubleVector(x, y)
}
}
private fun buildHints(
dataPoints: Iterable,
ctx: GeomContext,
helper: GeomHelper,
boundTransform: (p: DataPointAesthetics) -> DoubleVector
) {
val multiPointDataList = MultiPointDataConstructor.createMultiPointDataByGroup(
dataPoints,
MultiPointDataConstructor.singlePointAppender { p ->
boundTransform(p).let { helper.toClient(it, p) }
},
MultiPointDataConstructor.reducer(0.999, false)
)
val targetCollector = getGeomTargetCollector(ctx)
val colorMarkerMapper = HintColorUtil.createColorMarkerMapper(GeomKind.AREA_RIDGES, ctx)
for (multiPointData in multiPointDataList) {
targetCollector.addPath(
multiPointData.points,
multiPointData.localToGlobalIndex,
GeomTargetCollector.TooltipParams(
markerColors = colorMarkerMapper(multiPointData.aes)
),
if (ctx.flipped) {
TipLayoutHint.Kind.VERTICAL_TOOLTIP
} else {
TipLayoutHint.Kind.HORIZONTAL_TOOLTIP
}
)
}
}
override fun heightSpan(p: DataPointAesthetics, coordAes: Aes, resolution: Double, isDiscrete: Boolean): DoubleSpan? {
val sizeAes = Aes.HEIGHT
val scaledResolution = resolution * this.scale
val loc = p[coordAes]
val size = p[sizeAes]
return if (SeriesUtil.allFinite(loc, size)) {
loc!!
val expand = scaledResolution * size!!
if (size >= this.minHeight) {
DoubleSpan(loc, loc + expand)
} else {
null
}
} else {
null
}
}
companion object {
const val DEF_SCALE = 1.0
const val DEF_MIN_HEIGHT = 0.0
const val DEF_QUANTILE_LINES = false
const val HANDLES_GROUPS = true
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy