From 5b4c0865c7c8e60c26679dbaf3dfb6f7489c13d8 Mon Sep 17 00:00:00 2001 From: Ivan Seleznev Date: Tue, 17 Mar 2026 12:13:44 +0100 Subject: [PATCH 01/10] Fix dropped point handling --- .../plot/common/model/EmptyGeomContext.kt | 9 ++- .../letsPlot/core/plot/base/BogusContext.kt | 10 +-- .../core/plot/base/BogusCoordinateSystem.kt | 31 ++++++++ .../letsPlot/core/plot/base/GeomContext.kt | 5 +- .../letsPlot/core/plot/base/geom/AreaGeom.kt | 26 +++---- .../core/plot/base/geom/AreaRidgesGeom.kt | 2 +- .../letsPlot/core/plot/base/geom/BandGeom.kt | 2 +- .../letsPlot/core/plot/base/geom/CurveGeom.kt | 2 +- .../core/plot/base/geom/Density2dGeom.kt | 2 +- .../core/plot/base/geom/Density2dfGeom.kt | 2 +- .../core/plot/base/geom/DensityGeom.kt | 4 -- .../core/plot/base/geom/DotplotGeom.kt | 3 +- .../core/plot/base/geom/ErrorBarGeom.kt | 2 +- .../letsPlot/core/plot/base/geom/GeomBase.kt | 29 ++------ .../letsPlot/core/plot/base/geom/HLineGeom.kt | 7 +- .../letsPlot/core/plot/base/geom/LineGeom.kt | 3 +- .../core/plot/base/geom/LineRangeGeom.kt | 2 +- .../core/plot/base/geom/LollipopGeom.kt | 2 +- .../letsPlot/core/plot/base/geom/PathGeom.kt | 17 +++-- .../letsPlot/core/plot/base/geom/PointGeom.kt | 7 +- .../core/plot/base/geom/PointRangeGeom.kt | 2 +- .../core/plot/base/geom/PolygonGeom.kt | 10 ++- .../core/plot/base/geom/RibbonGeom.kt | 20 +++--- .../core/plot/base/geom/SegmentGeom.kt | 2 +- .../letsPlot/core/plot/base/geom/SinaGeom.kt | 12 +--- .../core/plot/base/geom/SmoothGeom.kt | 2 +- .../letsPlot/core/plot/base/geom/SpokeGeom.kt | 2 +- .../letsPlot/core/plot/base/geom/StepGeom.kt | 10 ++- .../core/plot/base/geom/TextRepelGeom.kt | 2 +- .../letsPlot/core/plot/base/geom/VLineGeom.kt | 7 +- .../core/plot/base/geom/ViolinGeom.kt | 2 +- .../core/plot/base/geom/YDotplotGeom.kt | 6 +- .../plot/base/geom/util/BarTooltipHelper.kt | 2 +- .../core/plot/base/geom/util/GeomUtil.kt | 56 --------------- .../plot/base/geom/util/HexagonsHelper.kt | 2 +- .../core/plot/base/geom/util/HintColorUtil.kt | 8 ++- .../core/plot/base/geom/util/LinesHelper.kt | 72 ++++++++++++++++--- .../base/geom/util/RectangleTooltipHelper.kt | 2 +- .../base/geom/util/TargetCollectorHelper.kt | 4 +- .../core/plot/base/geom/util/TextHelper.kt | 2 +- .../core/plot/base/geom/AreaGeomTest.kt | 2 + .../core/plot/base/geom/ErrorBarGeomTest.kt | 3 + .../base/tooltip/loc/PolygonEdgeCasesTest.kt | 8 ++- .../builder/assemble/GeomContextBuilder.kt | 50 ++++++++----- .../builder/assemble/ImmutableGeomContext.kt | 6 +- .../builder/frame/FrameOfReferenceBase.kt | 1 + .../core/plot/livemap/DataPointsConverter.kt | 17 +++-- 47 files changed, 266 insertions(+), 213 deletions(-) rename plot-base/src/{commonTest => commonMain}/kotlin/org/jetbrains/letsPlot/core/plot/base/BogusContext.kt (94%) create mode 100644 plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/BogusCoordinateSystem.kt diff --git a/demo/common-plot/src/commonMain/kotlin/demo/plot/common/model/EmptyGeomContext.kt b/demo/common-plot/src/commonMain/kotlin/demo/plot/common/model/EmptyGeomContext.kt index ea4ee3d2dc8..7e499e4f756 100644 --- a/demo/common-plot/src/commonMain/kotlin/demo/plot/common/model/EmptyGeomContext.kt +++ b/demo/common-plot/src/commonMain/kotlin/demo/plot/common/model/EmptyGeomContext.kt @@ -34,11 +34,6 @@ class EmptyGeomContext : GeomContext { throw IllegalStateException("Not available in an empty geom context") } - override fun withTargetCollector(targetCollector: GeomTargetCollector): GeomContext { - throw IllegalStateException("Not available in an empty geom context") - } - - override fun getDefaultFormatter(aes: Aes<*>): (Any) -> String { throw IllegalStateException("Not available in an empty geom context") } @@ -63,6 +58,10 @@ class EmptyGeomContext : GeomContext { throw IllegalStateException("Not available in an empty geom context") } + override fun geomKind(): GeomKind { + throw IllegalStateException("Not available in an empty geom context") + } + override fun isMappedAes(aes: Aes<*>): Boolean = false override fun estimateTextSize( text: String, diff --git a/plot-base/src/commonTest/kotlin/org/jetbrains/letsPlot/core/plot/base/BogusContext.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/BogusContext.kt similarity index 94% rename from plot-base/src/commonTest/kotlin/org/jetbrains/letsPlot/core/plot/base/BogusContext.kt rename to plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/BogusContext.kt index 66aff67f53b..3d52af4be40 100644 --- a/plot-base/src/commonTest/kotlin/org/jetbrains/letsPlot/core/plot/base/BogusContext.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/BogusContext.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023. JetBrains s.r.o. + * Copyright (c) 2026. JetBrains s.r.o. * Use of this source code is governed by the MIT license that can be found in the LICENSE file. */ @@ -31,10 +31,6 @@ object BogusContext : GeomContext { error("Not available in a bogus geom context") } - override fun withTargetCollector(targetCollector: GeomTargetCollector): GeomContext { - error("Not available in a bogus geom context") - } - override fun isMappedAes(aes: Aes<*>): Boolean { error("Not available in a bogus geom context") } @@ -63,6 +59,10 @@ object BogusContext : GeomContext { // do nothing } + override fun geomKind(): GeomKind { + error("Not available in a bogus geom context") + } + override fun estimateTextSize( text: String, family: String, diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/BogusCoordinateSystem.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/BogusCoordinateSystem.kt new file mode 100644 index 00000000000..541ca5bdb01 --- /dev/null +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/BogusCoordinateSystem.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2026. JetBrains s.r.o. + * Use of this source code is governed by the MIT license that can be found in the LICENSE file. + */ + +package org.jetbrains.letsPlot.core.plot.base + +import org.jetbrains.letsPlot.commons.geometry.DoubleVector + +object BogusCoordinateSystem : CoordinateSystem { + override val isLinear: Boolean + get() = error("Not available in a bogus coordinate system") + override val isPolar: Boolean + get() = error("Not available in a bogus coordinate system") + + override fun toClient(p: DoubleVector): DoubleVector? { + error("Not available in a bogus coordinate system") + } + + override fun fromClient(p: DoubleVector): DoubleVector? { + error("Not available in a bogus coordinate system") + } + + override fun unitSize(p: DoubleVector): DoubleVector { + error("Not available in a bogus coordinate system") + } + + override fun flip(): CoordinateSystem { + error("Not available in a bogus coordinate system") + } +} \ No newline at end of file diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/GeomContext.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/GeomContext.kt index 5eed7bf09b6..af82a543c67 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/GeomContext.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/GeomContext.kt @@ -26,7 +26,8 @@ interface GeomContext { */ fun getAesBounds(): DoubleRectangle - fun withTargetCollector(targetCollector: GeomTargetCollector): GeomContext +// not used +// fun withTargetCollector(targetCollector: GeomTargetCollector): GeomContext fun isMappedAes(aes: Aes<*>): Boolean @@ -53,4 +54,6 @@ interface GeomContext { fun getScaleFactor(): Double fun consumeMessages(messages: List) + + fun geomKind(): GeomKind } diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/AreaGeom.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/AreaGeom.kt index 7fbdd6d24bb..7933971981f 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/AreaGeom.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/AreaGeom.kt @@ -26,7 +26,7 @@ open class AreaGeom : GeomBase() { override fun rangeIncludesZero(aes: Aes<*>): Boolean = (aes == Aes.Y) - override fun prepareDataPoints(dataPoints: Iterable): Iterable { + override fun filterDataPoints(dataPoints: Iterable): Iterable { val data = GeomUtil.with_X(dataPoints) return GeomUtil.ordered_X(data) } @@ -38,21 +38,24 @@ open class AreaGeom : GeomBase() { coord: CoordinateSystem, ctx: GeomContext ) { - val helper = LinesHelper(pos, coord, ctx) - helper.setResamplingEnabled(!coord.isLinear && !flat) + val linesHelper = LinesHelper(pos, coord, ctx) + linesHelper.setResamplingEnabled(!coord.isLinear && !flat) // Alpha is disabled for strokes (but still applies to fill). - helper.setAlphaEnabled(false) + linesHelper.setAlphaEnabled(false) val quantilesHelper = QuantilesHelper(pos, coord, ctx, quantiles) - val targetCollectorHelper = TargetCollectorHelper(tooltipsGeomKind(), ctx) + val targetCollectorHelper = TargetCollectorHelper(ctx) - val dataPoints = dataPoints(aesthetics) - val closePath = helper.meetsRadarPlotReq() + val source = aesthetics.dataPoints() + val dataPoints = filterDataPoints(source) + val filteredPointsCount = source.count() - dataPoints.count() + + val closePath = linesHelper.meetsRadarPlotReq() dataPoints.sortedByDescending(DataPointAesthetics::group).groupBy(DataPointAesthetics::group) .forEach { (_, groupDataPoints) -> quantilesHelper.splitByQuantiles(groupDataPoints, Aes.X).forEach { points -> - val bands = helper.renderBands( + val bands = linesHelper.renderBands( points, TO_LOCATION_X_Y, TO_LOCATION_X_ZERO_WITH_FINITE_Y, @@ -61,9 +64,9 @@ open class AreaGeom : GeomBase() { ) root.appendNodes(bands) - val upperPoints = helper.createPathData(points, TO_LOCATION_X_Y, closePath) + val upperPoints = linesHelper.createPathData(points, TO_LOCATION_X_Y, closePath) - val line = helper.renderPaths(upperPoints, filled = false) + val line = linesHelper.renderPaths(upperPoints, filled = false) root.appendNodes(line) targetCollectorHelper.addVariadicPaths(upperPoints) } @@ -72,6 +75,7 @@ open class AreaGeom : GeomBase() { createQuantileLines(groupDataPoints, quantilesHelper).forEach(root::add) } } + reportDroppedPoints(filteredPointsCount + linesHelper.getDroppedPointsCount(), ctx) } private fun createQuantileLines( @@ -83,8 +87,6 @@ open class AreaGeom : GeomBase() { return quantilesHelper.getQuantileLineElements(dataPoints, Aes.X, toLocationBoundStart, toLocationBoundEnd) } - protected open fun tooltipsGeomKind() = GeomKind.AREA - companion object { const val DEF_QUANTILE_LINES = false const val HANDLES_GROUPS = true diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/AreaRidgesGeom.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/AreaRidgesGeom.kt index d64437f78fe..e9ba62fa265 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/AreaRidgesGeom.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/AreaRidgesGeom.kt @@ -68,7 +68,7 @@ class AreaRidgesGeom : GeomBase(), WithHeight { val quantilesHelper = QuantilesHelper(pos, coord, ctx, quantiles, Aes.Y) val boundTransform = toLocationBound(ctx) - val targetCollectorHelper = TargetCollectorHelper(GeomKind.AREA_RIDGES, ctx) + val targetCollectorHelper = TargetCollectorHelper(ctx) quantilesHelper.splitByQuantiles(dataPoints, Aes.X).forEach { points -> val paths = helper.createBands( diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/BandGeom.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/BandGeom.kt index 6d6b6674eab..3b9ab1e2587 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/BandGeom.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/BandGeom.kt @@ -56,7 +56,7 @@ class BandGeom(private val isVertical: Boolean) : GeomBase() { // tooltip val tooltipParams = GeomTargetCollector.TooltipParams( tipLayoutHints = HintsCollection(p, geomHelper).hints, - markerColors = HintColorUtil.createColorMarkerMapper(GeomKind.BAND, ctx)(p) + markerColors = HintColorUtil.createColorMarkerMapper(ctx)(p) ) geomHelper.toClient(rect.flipIf(!isVertical), p)?.let { r -> diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/CurveGeom.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/CurveGeom.kt index 76123d6826b..2fc2cc2c672 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/CurveGeom.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/CurveGeom.kt @@ -37,7 +37,7 @@ class CurveGeom : GeomBase() { coord: CoordinateSystem, ctx: GeomContext ) { - val tooltipHelper = TargetCollectorHelper(GeomKind.CURVE, ctx) + val tooltipHelper = TargetCollectorHelper(ctx) val geomHelper = GeomHelper(pos, coord, ctx) val svgElementHelper = geomHelper.createSvgElementHelper() diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/Density2dGeom.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/Density2dGeom.kt index dbb99d26215..98849b663ec 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/Density2dGeom.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/Density2dGeom.kt @@ -9,6 +9,6 @@ class Density2dGeom : ContourGeom() { companion object { // val RENDERS = ContourGeom.RENDERS - val HANDLES_GROUPS = ContourGeom.HANDLES_GROUPS + const val HANDLES_GROUPS = ContourGeom.HANDLES_GROUPS } } diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/Density2dfGeom.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/Density2dfGeom.kt index 7a8d63cb615..9d8f78c0f24 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/Density2dfGeom.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/Density2dfGeom.kt @@ -9,6 +9,6 @@ class Density2dfGeom : ContourfGeom() { companion object { // val RENDERS: List> = ContourfGeom.RENDERS - val HANDLES_GROUPS = ContourfGeom.HANDLES_GROUPS + const val HANDLES_GROUPS = ContourfGeom.HANDLES_GROUPS } } diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/DensityGeom.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/DensityGeom.kt index 9487dcca00a..acdc14aa953 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/DensityGeom.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/DensityGeom.kt @@ -5,12 +5,8 @@ package org.jetbrains.letsPlot.core.plot.base.geom -import org.jetbrains.letsPlot.core.plot.base.GeomKind - class DensityGeom : AreaGeom() { - override fun tooltipsGeomKind() = GeomKind.DENSITY - companion object { const val DEF_QUANTILE_LINES = AreaGeom.DEF_QUANTILE_LINES const val HANDLES_GROUPS = AreaGeom.HANDLES_GROUPS diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/DotplotGeom.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/DotplotGeom.kt index ea51689ff21..f0f8cf2c3cf 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/DotplotGeom.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/DotplotGeom.kt @@ -9,7 +9,6 @@ import org.jetbrains.letsPlot.commons.geometry.DoubleRectangle import org.jetbrains.letsPlot.commons.geometry.DoubleVector import org.jetbrains.letsPlot.commons.interval.DoubleSpan import org.jetbrains.letsPlot.core.plot.base.* -import org.jetbrains.letsPlot.core.plot.base.GeomKind.DOT_PLOT import org.jetbrains.letsPlot.core.plot.base.aes.AesScaling import org.jetbrains.letsPlot.core.plot.base.geom.util.GeomHelper import org.jetbrains.letsPlot.core.plot.base.geom.util.GeomUtil @@ -132,7 +131,7 @@ open class DotplotGeom : GeomBase(), WithWidth { DoubleRectangle(center.add(shiftToOrigin.flip()), dimension.flip()) else DoubleRectangle(center.add(shiftToOrigin), dimension) - val colorMarkerMapper = createColorMarkerMapper(DOT_PLOT, ctx) + val colorMarkerMapper = createColorMarkerMapper(ctx) ctx.targetCollector.addRectangle( p.index(), diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/ErrorBarGeom.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/ErrorBarGeom.kt index de83fc43dfa..50860d3af74 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/ErrorBarGeom.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/ErrorBarGeom.kt @@ -34,7 +34,7 @@ class ErrorBarGeom : GeomBase(), WithWidth { ctx: GeomContext ) { val geomHelper = GeomHelper(pos, coord, ctx) - val colorsByDataPoint = HintColorUtil.createColorMarkerMapper(GeomKind.ERROR_BAR, ctx) + val colorsByDataPoint = HintColorUtil.createColorMarkerMapper(ctx) val tooltipHelper = RectangleTooltipHelper( pos = pos, coord = coord, diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/GeomBase.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/GeomBase.kt index ca0fe8ba5b4..43b0d1102cc 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/GeomBase.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/GeomBase.kt @@ -9,10 +9,10 @@ import org.jetbrains.letsPlot.commons.geometry.DoubleRectangle import org.jetbrains.letsPlot.commons.interval.DoubleSpan import org.jetbrains.letsPlot.core.plot.base.* import org.jetbrains.letsPlot.core.plot.base.geom.legend.GenericLegendKeyElementFactory -import org.jetbrains.letsPlot.core.plot.base.tooltip.GeomTargetCollector import org.jetbrains.letsPlot.core.plot.base.render.LegendKeyElementFactory import org.jetbrains.letsPlot.core.plot.base.render.SvgRoot import org.jetbrains.letsPlot.core.plot.base.render.svg.LinePath +import org.jetbrains.letsPlot.core.plot.base.tooltip.GeomTargetCollector import org.jetbrains.letsPlot.datamodel.svg.dom.SvgGElement import org.jetbrains.letsPlot.datamodel.svg.dom.slim.SvgSlimElements import org.jetbrains.letsPlot.datamodel.svg.dom.slim.SvgSlimGroup @@ -23,9 +23,6 @@ abstract class GeomBase : Geom { override val legendKeyElementFactory: LegendKeyElementFactory get() = GenericLegendKeyElementFactory() - protected open val geomName: String = "unhandled_geom" - private var nullCounter = 0 - override fun build( root: SvgRoot, aesthetics: Aesthetics, @@ -34,9 +31,6 @@ abstract class GeomBase : Geom { ctx: GeomContext ) { buildIntern(root, aesthetics, pos, coord, ctx) - if (SHOW_NA_MESSAGES) { - ctx.consumeMessages(getMessages()) - } } open fun preferableNullDomain(aes: Aes<*>): DoubleSpan { @@ -47,23 +41,14 @@ abstract class GeomBase : Geom { return ctx.targetCollector } - open fun prepareDataPoints(dataPoints: Iterable): Iterable { + open fun filterDataPoints(dataPoints: Iterable): Iterable { return dataPoints } - protected fun dataPoints(aesthetics: Aesthetics): Iterable { - val source = aesthetics.dataPoints() - val result = prepareDataPoints(source) - nullCounter = source.count() - result.count() - return result - } - - fun addNulls(count: Int) { - nullCounter += count - } - - private fun getMessages(): List { - return if (nullCounter > 0) listOf("$geomName: removed $nullCounter data point(s)") else emptyList() + fun reportDroppedPoints(count: Int, ctx: GeomContext) { + if (SHOW_NA_MESSAGES && count > 0) { + ctx.consumeMessages(listOf("${ctx.geomKind().name.lowercase()}: removed $count data point(s)")) + } } protected abstract fun buildIntern( @@ -75,7 +60,7 @@ abstract class GeomBase : Geom { ) companion object { - private const val SHOW_NA_MESSAGES = false + private const val SHOW_NA_MESSAGES = true fun wrap(slimGroup: SvgSlimGroup): SvgGElement { val g = SvgGElement() diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/HLineGeom.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/HLineGeom.kt index 61b935e96a4..a1a8ba0baec 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/HLineGeom.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/HLineGeom.kt @@ -6,7 +6,10 @@ package org.jetbrains.letsPlot.core.plot.base.geom import org.jetbrains.letsPlot.commons.geometry.DoubleVector -import org.jetbrains.letsPlot.core.plot.base.* +import org.jetbrains.letsPlot.core.plot.base.Aesthetics +import org.jetbrains.letsPlot.core.plot.base.CoordinateSystem +import org.jetbrains.letsPlot.core.plot.base.GeomContext +import org.jetbrains.letsPlot.core.plot.base.PositionAdjustment import org.jetbrains.letsPlot.core.plot.base.geom.legend.HLineLegendKeyElementFactory import org.jetbrains.letsPlot.core.plot.base.geom.util.GeomHelper import org.jetbrains.letsPlot.core.plot.base.geom.util.TargetCollectorHelper @@ -25,7 +28,7 @@ class HLineGeom : GeomBase() { coord: CoordinateSystem, ctx: GeomContext ) { - val tooltipHelper = TargetCollectorHelper(GeomKind.H_LINE, ctx) + val tooltipHelper = TargetCollectorHelper(ctx) val geomHelper = GeomHelper(pos, coord, ctx) val helper = geomHelper.createSvgElementHelper() .setStrokeAlphaEnabled(true) diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/LineGeom.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/LineGeom.kt index dc897fa564a..d89ba3fa4df 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/LineGeom.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/LineGeom.kt @@ -9,9 +9,8 @@ import org.jetbrains.letsPlot.core.plot.base.DataPointAesthetics import org.jetbrains.letsPlot.core.plot.base.geom.util.GeomUtil open class LineGeom : PathGeom() { - override val geomName: String = "line" - override fun prepareDataPoints(dataPoints: Iterable): Iterable { + override fun filterDataPoints(dataPoints: Iterable): Iterable { val data = GeomUtil.with_X(dataPoints) return GeomUtil.ordered_X(data) } diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/LineRangeGeom.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/LineRangeGeom.kt index 8bb01176485..5dd957373ef 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/LineRangeGeom.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/LineRangeGeom.kt @@ -35,7 +35,7 @@ class LineRangeGeom : GeomBase() { val geomHelper = GeomHelper(pos, coord, ctx) val helper = geomHelper.createSvgElementHelper() helper.setStrokeAlphaEnabled(true) - val colorsByDataPoint = HintColorUtil.createColorMarkerMapper(GeomKind.LINE_RANGE, ctx) + val colorsByDataPoint = HintColorUtil.createColorMarkerMapper(ctx) val tooltipHelper = RectangleTooltipHelper( pos = pos, coord = coord, diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/LollipopGeom.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/LollipopGeom.kt index 19c01cc6b86..eb561333701 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/LollipopGeom.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/LollipopGeom.kt @@ -50,7 +50,7 @@ class LollipopGeom : GeomBase(), WithWidth, WithHeight { ) { val helper = GeomHelper(pos, coord, ctx) val targetCollector = getGeomTargetCollector(ctx) - val colorsByDataPoint = HintColorUtil.createColorMarkerMapper(GeomKind.LOLLIPOP, ctx) + val colorsByDataPoint = HintColorUtil.createColorMarkerMapper(ctx) val lollipops = mutableListOf() for (p in aesthetics.dataPoints()) { diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/PathGeom.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/PathGeom.kt index d689b186ed3..3f29e790820 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/PathGeom.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/PathGeom.kt @@ -5,7 +5,10 @@ package org.jetbrains.letsPlot.core.plot.base.geom -import org.jetbrains.letsPlot.core.plot.base.* +import org.jetbrains.letsPlot.core.plot.base.Aesthetics +import org.jetbrains.letsPlot.core.plot.base.CoordinateSystem +import org.jetbrains.letsPlot.core.plot.base.GeomContext +import org.jetbrains.letsPlot.core.plot.base.PositionAdjustment import org.jetbrains.letsPlot.core.plot.base.geom.util.GeomUtil import org.jetbrains.letsPlot.core.plot.base.geom.util.LinesHelper import org.jetbrains.letsPlot.core.plot.base.geom.util.TargetCollectorHelper @@ -18,8 +21,6 @@ open class PathGeom : GeomBase() { var flat: Boolean = false var geodesic: Boolean = false - override val geomName: String = "path" - override val legendKeyElementFactory: LegendKeyElementFactory get() = HLineGeom.LEGEND_KEY_ELEMENT_FACTORY @@ -30,18 +31,22 @@ open class PathGeom : GeomBase() { coord: CoordinateSystem, ctx: GeomContext ) { - val dataPoints = dataPoints(aesthetics) - val linesHelper = LinesHelper(pos, coord, ctx, ::addNulls) + val source = aesthetics.dataPoints() + val dataPoints = filterDataPoints(source) + val filteredPointsCount = source.count() - dataPoints.count() + + val linesHelper = LinesHelper(pos, coord, ctx) linesHelper.setResamplingEnabled(!coord.isLinear && !flat) val closePath = linesHelper.meetsRadarPlotReq() val pathData = linesHelper.createPathData(dataPoints, GeomUtil.TO_LOCATION_X_Y, closePath) - val targetCollectorHelper = TargetCollectorHelper(GeomKind.PATH, ctx) + val targetCollectorHelper = TargetCollectorHelper(ctx) targetCollectorHelper.addVariadicPaths(pathData) val svgPath = linesHelper.renderPaths(pathData, filled = false) root.appendNodes(svgPath) + reportDroppedPoints(filteredPointsCount + linesHelper.getDroppedPointsCount(), ctx) } companion object { diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/PointGeom.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/PointGeom.kt index 6b3ff2c02c1..48bfc18b94e 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/PointGeom.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/PointGeom.kt @@ -9,17 +9,16 @@ import org.jetbrains.letsPlot.core.plot.base.* import org.jetbrains.letsPlot.core.plot.base.aes.AesScaling import org.jetbrains.letsPlot.core.plot.base.geom.util.GeomHelper import org.jetbrains.letsPlot.core.plot.base.geom.util.HintColorUtil -import org.jetbrains.letsPlot.core.plot.base.tooltip.GeomTargetCollector import org.jetbrains.letsPlot.core.plot.base.render.LegendKeyElementFactory import org.jetbrains.letsPlot.core.plot.base.render.SvgRoot import org.jetbrains.letsPlot.core.plot.base.render.point.PointShapeSvg +import org.jetbrains.letsPlot.core.plot.base.tooltip.GeomTargetCollector import org.jetbrains.letsPlot.datamodel.svg.dom.slim.SvgSlimElements open class PointGeom : GeomBase() { var animation: Any? = null var sizeUnit: String? = null - override val geomName: String = "point" override val legendKeyElementFactory: LegendKeyElementFactory get() = PointLegendKeyElementFactory() @@ -33,7 +32,7 @@ open class PointGeom : GeomBase() { ) { val helper = GeomHelper(pos, coord, ctx) val targetCollector = getGeomTargetCollector(ctx) - val colorsByDataPoint = HintColorUtil.createColorMarkerMapper(GeomKind.POINT, ctx) + val colorsByDataPoint = HintColorUtil.createColorMarkerMapper(ctx) val count = aesthetics.dataPointCount() val slimGroup = SvgSlimElements.g(count) @@ -66,7 +65,7 @@ open class PointGeom : GeomBase() { o.appendTo(slimGroup) goodPointsCount += 1 } - addNulls(count - goodPointsCount) + reportDroppedPoints(count - goodPointsCount, ctx) root.add(wrap(slimGroup)) } diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/PointRangeGeom.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/PointRangeGeom.kt index f67fa582d14..2aafa12851e 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/PointRangeGeom.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/PointRangeGeom.kt @@ -40,7 +40,7 @@ class PointRangeGeom : GeomBase() { val geomHelper = GeomHelper(pos, coord, ctx) val helper = geomHelper.createSvgElementHelper() helper.setStrokeAlphaEnabled(true) - val colorsByDataPoint = HintColorUtil.createColorMarkerMapper(GeomKind.POINT_RANGE, ctx) + val colorsByDataPoint = HintColorUtil.createColorMarkerMapper(ctx) val tooltipHelper = RectangleTooltipHelper( pos = pos, coord = coord, diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/PolygonGeom.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/PolygonGeom.kt index e0e976dec94..f1160fc80ef 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/PolygonGeom.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/PolygonGeom.kt @@ -13,7 +13,7 @@ import org.jetbrains.letsPlot.core.plot.base.render.SvgRoot open class PolygonGeom : GeomBase() { - override fun prepareDataPoints(dataPoints: Iterable): Iterable { + override fun filterDataPoints(dataPoints: Iterable): Iterable { return GeomUtil.with_X_Y(dataPoints) } @@ -24,16 +24,20 @@ open class PolygonGeom : GeomBase() { coord: CoordinateSystem, ctx: GeomContext ) { - val dataPoints = dataPoints(aesthetics) + val source = aesthetics.dataPoints() + val dataPoints = filterDataPoints(source) + val filteredPointsCount = source.count() - dataPoints.count() + val linesHelper = LinesHelper(pos, coord, ctx) linesHelper.setResamplingEnabled(coord.isPolar) - val targetCollectorHelper = TargetCollectorHelper(GeomKind.POLYGON, ctx) + val targetCollectorHelper = TargetCollectorHelper(ctx) linesHelper.createPolygon(dataPoints, GeomUtil.TO_LOCATION_X_Y).forEach { (svg, polygonData) -> targetCollectorHelper.addPolygons(polygonData) root.add(svg) } + reportDroppedPoints(filteredPointsCount + linesHelper.getDroppedPointsCount(), ctx) } companion object { diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/RibbonGeom.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/RibbonGeom.kt index 9a33851b3e6..b80b7c83011 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/RibbonGeom.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/RibbonGeom.kt @@ -21,7 +21,7 @@ import org.jetbrains.letsPlot.core.plot.base.tooltip.TipLayoutHint.Kind.VERTICAL class RibbonGeom : GeomBase() { - override fun prepareDataPoints(dataPoints: Iterable): Iterable { + override fun filterDataPoints(dataPoints: Iterable): Iterable { val data = GeomUtil.with_X(dataPoints) return GeomUtil.ordered_X(data) } @@ -33,25 +33,29 @@ class RibbonGeom : GeomBase() { coord: CoordinateSystem, ctx: GeomContext ) { - val dataPoints = dataPoints(aesthetics) - val helper = LinesHelper(pos, coord, ctx) + val source = aesthetics.dataPoints() + val dataPoints = filterDataPoints(source) + val filteredPointsCount = source.count() - dataPoints.count() - val paths = helper.createBands(dataPoints, TO_LOCATION_X_YMAX_WITH_FINITE_YMIN, TO_LOCATION_X_YMIN_WITH_FINITE_YMAX) + val linesHelper = LinesHelper(pos, coord, ctx) + + val paths = linesHelper.createBands(dataPoints, TO_LOCATION_X_YMAX_WITH_FINITE_YMIN, TO_LOCATION_X_YMIN_WITH_FINITE_YMAX) root.appendNodes(paths) //if you want to retain the side edges of ribbon: //comment out the following codes, and switch decorate method in LinesHelper.createBands - helper.setAlphaEnabled(false) + linesHelper.setAlphaEnabled(false) - root.appendNodes(helper.createLines(dataPoints, TO_LOCATION_X_YMAX)) - root.appendNodes(helper.createLines(dataPoints, TO_LOCATION_X_YMIN)) + root.appendNodes(linesHelper.createLines(dataPoints, TO_LOCATION_X_YMAX)) + root.appendNodes(linesHelper.createLines(dataPoints, TO_LOCATION_X_YMIN)) buildHints(aesthetics, pos, coord, ctx) + reportDroppedPoints(filteredPointsCount + linesHelper.getDroppedPointsCount(), ctx) } private fun buildHints(aesthetics: Aesthetics, pos: PositionAdjustment, coord: CoordinateSystem, ctx: GeomContext) { val helper = GeomHelper(pos, coord, ctx) - val colorMapper = HintColorUtil.createColorMarkerMapper(GeomKind.RIBBON, ctx) + val colorMapper = HintColorUtil.createColorMarkerMapper(ctx) val hint = HintsCollection.HintConfigFactory() .defaultObjectRadius(0.0) .defaultKind(HORIZONTAL_TOOLTIP.takeUnless { ctx.flipped } ?: VERTICAL_TOOLTIP) diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/SegmentGeom.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/SegmentGeom.kt index cdc387ffad0..2915bd66aef 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/SegmentGeom.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/SegmentGeom.kt @@ -32,7 +32,7 @@ class SegmentGeom : GeomBase() { coord: CoordinateSystem, ctx: GeomContext ) { - val tooltipHelper = TargetCollectorHelper(GeomKind.SEGMENT, ctx) + val tooltipHelper = TargetCollectorHelper(ctx) val geomHelper = GeomHelper(pos, coord, ctx) val svgHelper = geomHelper .createSvgElementHelper() diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/SinaGeom.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/SinaGeom.kt index c618c31723e..050e985605b 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/SinaGeom.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/SinaGeom.kt @@ -6,13 +6,7 @@ package org.jetbrains.letsPlot.core.plot.base.geom import org.jetbrains.letsPlot.commons.geometry.DoubleVector -import org.jetbrains.letsPlot.core.plot.base.Aes -import org.jetbrains.letsPlot.core.plot.base.Aesthetics -import org.jetbrains.letsPlot.core.plot.base.CoordinateSystem -import org.jetbrains.letsPlot.core.plot.base.DataPointAesthetics -import org.jetbrains.letsPlot.core.plot.base.GeomContext -import org.jetbrains.letsPlot.core.plot.base.GeomKind -import org.jetbrains.letsPlot.core.plot.base.PositionAdjustment +import org.jetbrains.letsPlot.core.plot.base.* import org.jetbrains.letsPlot.core.plot.base.geom.util.GeomHelper import org.jetbrains.letsPlot.core.plot.base.geom.util.GeomUtil import org.jetbrains.letsPlot.core.plot.base.geom.util.HintColorUtil @@ -23,8 +17,6 @@ import org.jetbrains.letsPlot.core.plot.base.render.point.PointShapeSvg import org.jetbrains.letsPlot.core.plot.base.stat.BaseYDensityStat import org.jetbrains.letsPlot.core.plot.base.tooltip.GeomTargetCollector import org.jetbrains.letsPlot.datamodel.svg.dom.slim.SvgSlimElements -import kotlin.collections.component1 -import kotlin.collections.component2 import kotlin.random.Random class SinaGeom : GeomBase() { @@ -61,7 +53,7 @@ class SinaGeom : GeomBase() { val helper = GeomHelper(pos, coord, ctx) val quantilesHelper = QuantilesHelper(pos, coord, ctx, quantiles, Aes.X) val targetCollector = getGeomTargetCollector(ctx) - val colorsByDataPoint = HintColorUtil.createColorMarkerMapper(GeomKind.POINT, ctx) + val colorsByDataPoint = HintColorUtil.createColorMarkerMapper(ctx) val jitterTransform = toJitterTransform(ctx, rand) quantilesHelper.splitByQuantiles(dataPoints, Aes.Y).forEach { points -> diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/SmoothGeom.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/SmoothGeom.kt index e893bedd75c..12076e8ceb6 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/SmoothGeom.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/SmoothGeom.kt @@ -56,7 +56,7 @@ class SmoothGeom : GeomBase() { val paths = linesHelper.createPaths(dataPoints, GeomUtil.TO_LOCATION_X_Y) val objectRadius = 0.0 - val colorsByDataPoint = HintColorUtil.createColorMarkerMapper(GeomKind.SMOOTH, ctx) + val colorsByDataPoint = HintColorUtil.createColorMarkerMapper(ctx) paths.forEach { path -> path.aesthetics.windowed(size = 2) { (aes1, aes2) -> diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/SpokeGeom.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/SpokeGeom.kt index 1fc047c1f98..0e10dd7cf91 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/SpokeGeom.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/SpokeGeom.kt @@ -30,7 +30,7 @@ class SpokeGeom : GeomBase(), WithWidth, WithHeight { coord: CoordinateSystem, ctx: GeomContext ) { - val tooltipHelper = TargetCollectorHelper(GeomKind.SPOKE, ctx) + val tooltipHelper = TargetCollectorHelper(ctx) val geomHelper = GeomHelper(pos, coord, ctx) val svgElementHelper = geomHelper.createSvgElementHelper() .setStrokeAlphaEnabled(true) diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/StepGeom.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/StepGeom.kt index 19c967e51cb..6b44dee2166 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/StepGeom.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/StepGeom.kt @@ -22,7 +22,7 @@ class StepGeom : LineGeom() { myDirection = Direction.toDirection(dir) } // commit name: - override fun prepareDataPoints(dataPoints: Iterable): Iterable { + override fun filterDataPoints(dataPoints: Iterable): Iterable { // filter out points with NaN x-values but keep +/-Infinity (for 'padded' mode) val data = dataPoints.filter { p: DataPointAesthetics -> val x = p.x() @@ -39,7 +39,10 @@ class StepGeom : LineGeom() { coord: CoordinateSystem, ctx: GeomContext ) { - val dataPoints = dataPoints(aesthetics) + val source = aesthetics.dataPoints() + val dataPoints = filterDataPoints(source) + val filteredPointsCount = source.count() - dataPoints.count() + val linesHelper = LinesHelper(pos, coord, ctx) val pathDataList = linesHelper.createPaths(dataPoints, toLocationFor(overallAesBounds(ctx))) @@ -52,8 +55,9 @@ class StepGeom : LineGeom() { root.appendNodes(linePaths) - val targetCollectorHelper = TargetCollectorHelper(GeomKind.STEP, ctx) + val targetCollectorHelper = TargetCollectorHelper(ctx) targetCollectorHelper.addPaths(pathDataList) + reportDroppedPoints(filteredPointsCount + linesHelper.getDroppedPointsCount(), ctx) } private fun toLocationFor(viewPort: DoubleRectangle): (DataPointAesthetics) -> DoubleVector? { diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/TextRepelGeom.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/TextRepelGeom.kt index 2582a066723..4577a48eab6 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/TextRepelGeom.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/TextRepelGeom.kt @@ -60,7 +60,7 @@ open class TextRepelGeom: TextGeom() { .setStrokeAlphaEnabled(true) .setArrowSpec(arrowSpec) val targetCollector = getGeomTargetCollector(ctx) - val colorsByDataPoint = HintColorUtil.createColorMarkerMapper(GeomKind.TEXT, ctx) + val colorsByDataPoint = HintColorUtil.createColorMarkerMapper(ctx) val aesBoundsCenter = coord.toClient(ctx.getAesBounds())?.center val bounds = DoubleRectangle(DoubleVector.ZERO, ctx.getContentBounds().dimension) diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/VLineGeom.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/VLineGeom.kt index dc5e4e6794b..034ee7d4c15 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/VLineGeom.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/VLineGeom.kt @@ -6,7 +6,10 @@ package org.jetbrains.letsPlot.core.plot.base.geom import org.jetbrains.letsPlot.commons.geometry.DoubleVector -import org.jetbrains.letsPlot.core.plot.base.* +import org.jetbrains.letsPlot.core.plot.base.Aesthetics +import org.jetbrains.letsPlot.core.plot.base.CoordinateSystem +import org.jetbrains.letsPlot.core.plot.base.GeomContext +import org.jetbrains.letsPlot.core.plot.base.PositionAdjustment import org.jetbrains.letsPlot.core.plot.base.geom.legend.VLineLegendKeyElementFactory import org.jetbrains.letsPlot.core.plot.base.geom.util.GeomHelper import org.jetbrains.letsPlot.core.plot.base.geom.util.TargetCollectorHelper @@ -25,7 +28,7 @@ class VLineGeom : GeomBase() { coord: CoordinateSystem, ctx: GeomContext ) { - val tooltipHelper = TargetCollectorHelper(GeomKind.V_LINE, ctx) + val tooltipHelper = TargetCollectorHelper(ctx) val geomHelper = GeomHelper(pos, coord, ctx) val helper = geomHelper.createSvgElementHelper() helper.setStrokeAlphaEnabled(true) diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/ViolinGeom.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/ViolinGeom.kt index 8e2e5db923a..af5a8f4e9a1 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/ViolinGeom.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/ViolinGeom.kt @@ -112,7 +112,7 @@ class ViolinGeom : GeomBase() { boundTransform: (p: DataPointAesthetics) -> DoubleVector ) { val pathDataList = helper.createPaths(dataPoints, boundTransform) - val targetCollectorHelper = TargetCollectorHelper(GeomKind.VIOLIN, ctx) + val targetCollectorHelper = TargetCollectorHelper(ctx) targetCollectorHelper.addPaths(pathDataList) } diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/YDotplotGeom.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/YDotplotGeom.kt index 161e363e2e3..b0e1e5dbee8 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/YDotplotGeom.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/YDotplotGeom.kt @@ -12,10 +12,10 @@ import org.jetbrains.letsPlot.core.plot.base.* import org.jetbrains.letsPlot.core.plot.base.geom.util.GeomHelper import org.jetbrains.letsPlot.core.plot.base.geom.util.GeomUtil import org.jetbrains.letsPlot.core.plot.base.geom.util.HintColorUtil -import org.jetbrains.letsPlot.core.plot.base.tooltip.GeomTargetCollector -import org.jetbrains.letsPlot.core.plot.base.tooltip.TipLayoutHint import org.jetbrains.letsPlot.core.plot.base.render.LegendKeyElementFactory import org.jetbrains.letsPlot.core.plot.base.render.SvgRoot +import org.jetbrains.letsPlot.core.plot.base.tooltip.GeomTargetCollector +import org.jetbrains.letsPlot.core.plot.base.tooltip.TipLayoutHint import kotlin.math.abs class YDotplotGeom : DotplotGeom(), WithHeight { @@ -121,7 +121,7 @@ class YDotplotGeom : DotplotGeom(), WithHeight { DoubleVector(height, width) ) } - val colorMarkerMapper = HintColorUtil.createColorMarkerMapper(GeomKind.Y_DOT_PLOT, ctx) + val colorMarkerMapper = HintColorUtil.createColorMarkerMapper(ctx) ctx.targetCollector.addRectangle( p.index(), diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/util/BarTooltipHelper.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/util/BarTooltipHelper.kt index 814e99c60b0..01bb02afd62 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/util/BarTooltipHelper.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/util/BarTooltipHelper.kt @@ -22,7 +22,7 @@ object BarTooltipHelper { ctx: GeomContext, clientRectFactory: (DataPointAesthetics) -> DoubleRectangle?, fillColorMapper: (DataPointAesthetics) -> Color? = { null }, - colorMarkerMapper: (DataPointAesthetics) -> List = HintColorUtil.createColorMarkerMapper(null, ctx), + colorMarkerMapper: (DataPointAesthetics) -> List = HintColorUtil.createColorMarkerMapper(ctx), defaultTooltipKind: TipLayoutHint.Kind = VERTICAL_TOOLTIP.takeIf { ctx.flipped } ?: HORIZONTAL_TOOLTIP ) { val helper = GeomHelper(pos, coord, ctx) diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/util/GeomUtil.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/util/GeomUtil.kt index 1e1719cfbd1..6c6004155ac 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/util/GeomUtil.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/util/GeomUtil.kt @@ -8,7 +8,6 @@ package org.jetbrains.letsPlot.core.plot.base.geom.util import org.jetbrains.letsPlot.commons.geometry.DoubleRectangle import org.jetbrains.letsPlot.commons.geometry.DoubleVector import org.jetbrains.letsPlot.commons.intern.gcommon.collect.Ordering -import org.jetbrains.letsPlot.commons.intern.splitByNull import org.jetbrains.letsPlot.core.commons.data.SeriesUtil import org.jetbrains.letsPlot.core.plot.base.Aes import org.jetbrains.letsPlot.core.plot.base.DataPointAesthetics @@ -175,17 +174,6 @@ object GeomUtil { return dataPoints.filter { p -> p.defined(aes0) && p.defined(aes1) && p.defined(aes2) && p.defined(aes3) } } - private fun createGroups( - dataPoints: Iterable, - sorted: Boolean = false - ): Map> { - val map = dataPoints.groupBy { it.group()!! } - return when { - sorted -> map.toList().sortedBy { (g, _) -> g }.toMap() - else -> map - } - } - fun createPathDataFromRectangle( dataPoints: Iterable, pointTransform: ((DataPointAesthetics) -> List?) @@ -205,50 +193,6 @@ object GeomUtil { } } - // Builds a list of PathData splitting by group and null points. - fun createPaths( - dataPoints: Iterable, - pointTransform: ((DataPointAesthetics) -> DoubleVector?), - sorted: Boolean, - closePath: Boolean = false, - nullsCounter: (Int) -> Unit, - ): List { - val groups = createGroups(dataPoints, sorted).let { groups -> - if (closePath) { - groups.mapValues { (_, group) -> group + group.first() } - } else { - groups - } - } - - var nulls = 0 - var singlePointPaths = 0 - val result = groups.values - .map { aesthetics -> toPathPoints(aesthetics, pointTransform) } - .also { a -> - nulls += a.flatten().count { it == null } - } - .map { pathPoints -> pathPoints.splitByNull() } - .flatten() - .mapNotNull { - if (it.size == 1) singlePointPaths++ - PathData.create(it) - } - - nullsCounter(nulls + singlePointPaths) - - return result - } - - private fun toPathPoints( - dataPoints: Iterable, - pointTransform: ((DataPointAesthetics) -> DoubleVector?) - ): List { - return dataPoints.map { aes -> - pointTransform(aes)?.let { p -> PathPoint(aes, p) } - } - } - fun rectToGeometry(minX: Double, minY: Double, maxX: Double, maxY: Double): List { return listOf( DoubleVector(minX, minY), diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/util/HexagonsHelper.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/util/HexagonsHelper.kt index e5eba285dbb..52a297768fc 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/util/HexagonsHelper.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/util/HexagonsHelper.kt @@ -69,7 +69,7 @@ class HexagonsHelper( hex, p.index(), GeomTargetCollector.TooltipParams( - markerColors = createColorMarkerMapper(null, ctx)(p) + markerColors = createColorMarkerMapper(ctx)(p) ), tooltipKind = CURSOR_TOOLTIP ) diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/util/HintColorUtil.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/util/HintColorUtil.kt index 0c08da9d2b6..4e33793bc28 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/util/HintColorUtil.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/util/HintColorUtil.kt @@ -6,8 +6,11 @@ package org.jetbrains.letsPlot.core.plot.base.geom.util import org.jetbrains.letsPlot.commons.values.Color -import org.jetbrains.letsPlot.core.plot.base.* +import org.jetbrains.letsPlot.core.plot.base.DataPointAesthetics +import org.jetbrains.letsPlot.core.plot.base.GeomContext +import org.jetbrains.letsPlot.core.plot.base.GeomKind import org.jetbrains.letsPlot.core.plot.base.GeomKind.* +import org.jetbrains.letsPlot.core.plot.base.GeomMeta import org.jetbrains.letsPlot.core.plot.base.aes.AesInitValue import org.jetbrains.letsPlot.core.plot.base.aes.AestheticsUtil import org.jetbrains.letsPlot.core.plot.base.render.point.NamedShape @@ -38,11 +41,10 @@ object HintColorUtil { } fun createColorMarkerMapper( - geomKind: GeomKind?, ctx: GeomContext, ): (DataPointAesthetics) -> List { return createColorMarkerMapper( - geomKind, + ctx.geomKind(), isMappedFill = { p: DataPointAesthetics -> ctx.isMappedAes(p.fillAes) }, isMappedColor = { p: DataPointAesthetics -> ctx.isMappedAes(p.colorAes) } ) diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/util/LinesHelper.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/util/LinesHelper.kt index 4dbe4d40a83..5bcdf12d59f 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/util/LinesHelper.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/util/LinesHelper.kt @@ -7,6 +7,7 @@ package org.jetbrains.letsPlot.core.plot.base.geom.util import org.jetbrains.letsPlot.commons.geometry.DoubleVector import org.jetbrains.letsPlot.commons.intern.splitBy +import org.jetbrains.letsPlot.commons.intern.splitByNull import org.jetbrains.letsPlot.commons.intern.typedGeometry.algorithms.* import org.jetbrains.letsPlot.commons.intern.typedGeometry.algorithms.AdaptiveResampler.Companion.PIXEL_PRECISION import org.jetbrains.letsPlot.commons.intern.util.VectorAdapter @@ -17,17 +18,16 @@ import org.jetbrains.letsPlot.core.plot.base.* import org.jetbrains.letsPlot.core.plot.base.aes.AesScaling import org.jetbrains.letsPlot.core.plot.base.aes.AestheticsUtil import org.jetbrains.letsPlot.core.plot.base.geom.util.GeomUtil.createPathDataFromRectangle -import org.jetbrains.letsPlot.core.plot.base.geom.util.GeomUtil.createPaths import org.jetbrains.letsPlot.core.plot.base.render.svg.LinePath import org.jetbrains.letsPlot.datamodel.svg.dom.SvgNode open class LinesHelper( pos: PositionAdjustment, coord: CoordinateSystem, - ctx: GeomContext, - private val counter: (Int) -> Unit = {} // todo: remove default counter + ctx: GeomContext ) : GeomHelper(pos, coord, ctx) { + private var myDroppedPointsCount = 0 private var myAlphaEnabled = true protected var myResamplingEnabled = false protected var myResamplingPrecision = PIXEL_PRECISION @@ -83,7 +83,7 @@ open class LinesHelper( ): List { val domainData = createPaths( dataPoints, - locationTransform, sorted = true, closePath = closePath, nullsCounter = counter) + locationTransform, sorted = true, closePath = closePath) return toClientPaths(domainData) } @@ -91,7 +91,7 @@ open class LinesHelper( dataPoints: Iterable, locationTransform: (DataPointAesthetics) -> DoubleVector? = GeomUtil.TO_LOCATION_X_Y, ): List> { - val domainPathData = createPaths(dataPoints, locationTransform, sorted = true, closePath = false, nullsCounter = counter) + val domainPathData = createPaths(dataPoints, locationTransform, sorted = true, closePath = false) return createPolygon(domainPathData) } @@ -105,6 +105,10 @@ open class LinesHelper( return createPolygon(domainPathData) } + fun getDroppedPointsCount(): Int { + return myDroppedPointsCount + } + private fun createPolygon(domainPathData: Collection): List> { // split in domain space! after resampling coordinates may repeat and splitRings will return wrong results val domainPolygonData = domainPathData @@ -180,7 +184,59 @@ open class LinesHelper( dataPoints: Iterable, toLocation: (DataPointAesthetics) -> DoubleVector? ): List { - return createPaths(dataPoints, toClientLocation(toLocation), sorted = true, closePath = false, nullsCounter = counter) + return createPaths(dataPoints, toClientLocation(toLocation), sorted = true, closePath = false) + } + + // Builds a list of PathData splitting by group and null points. + fun createPaths( + dataPoints: Iterable, + pointTransform: ((DataPointAesthetics) -> DoubleVector?), + sorted: Boolean, + closePath: Boolean = false, + ): List { + val groups = createGroups(dataPoints, sorted).let { groups -> + if (closePath) { + groups.mapValues { (_, group) -> group + group.first() } + } else { + groups + } + } + + var nulls = 0 + var singlePointPaths = 0 + val result = groups.values + .map { aesthetics -> toPathPoints(aesthetics, pointTransform) } + .also { a -> nulls += a.flatten().count { it == null } } + .map { pathPoints -> pathPoints.splitByNull() } + .flatten() + .mapNotNull { + if (it.size == 1) singlePointPaths++ + PathData.create(it) + } + + myDroppedPointsCount += nulls + singlePointPaths + + return result + } + + private fun createGroups( + dataPoints: Iterable, + sorted: Boolean = false + ): Map> { + val map = dataPoints.groupBy { it.group()!! } + return when { + sorted -> map.toList().sortedBy { (g, _) -> g }.toMap() + else -> map + } + } + + private fun toPathPoints( + dataPoints: Iterable, + pointTransform: ((DataPointAesthetics) -> DoubleVector?) + ): List { + return dataPoints.map { aes -> + pointTransform(aes)?.let { p -> PathPoint(aes, p) } + } } fun createSteps(paths: Collection, horizontalThenVertical: Boolean): List { @@ -228,8 +284,8 @@ open class LinesHelper( simplifyBorders: Boolean, closePath: Boolean ): List { - val domainUpperPathData = createPaths(dataPoints, toLocationUpper, sorted = true, closePath, nullsCounter = counter) - val domainLowerPathData = createPaths(dataPoints, toLocationLower, sorted = true, closePath, nullsCounter = counter) + val domainUpperPathData = createPaths(dataPoints, toLocationUpper, sorted = true, closePath) + val domainLowerPathData = createPaths(dataPoints, toLocationLower, sorted = true, closePath) if (domainUpperPathData.isEmpty() || domainLowerPathData.isEmpty()) { return emptyList() diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/util/RectangleTooltipHelper.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/util/RectangleTooltipHelper.kt index 7b7a7679a74..444294f372c 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/util/RectangleTooltipHelper.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/util/RectangleTooltipHelper.kt @@ -21,7 +21,7 @@ class RectangleTooltipHelper( private val hintAesList: List> = emptyList(), private val tooltipKind: TipLayoutHint.Kind = VERTICAL_TOOLTIP.takeIf { ctx.flipped } ?: HORIZONTAL_TOOLTIP, private val fillColorMapper: (DataPointAesthetics) -> Color? = { null }, - private val colorMarkerMapper: (DataPointAesthetics) -> List = createColorMarkerMapper(null, ctx), + private val colorMarkerMapper: (DataPointAesthetics) -> List = createColorMarkerMapper(ctx), ) { private val helper = GeomHelper(pos, coord, ctx) diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/util/TargetCollectorHelper.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/util/TargetCollectorHelper.kt index b4c903c8490..0d6069c1294 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/util/TargetCollectorHelper.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/util/TargetCollectorHelper.kt @@ -9,7 +9,6 @@ import org.jetbrains.letsPlot.commons.geometry.DoubleVector import org.jetbrains.letsPlot.commons.intern.typedGeometry.algorithms.reduce import org.jetbrains.letsPlot.core.plot.base.DataPointAesthetics import org.jetbrains.letsPlot.core.plot.base.GeomContext -import org.jetbrains.letsPlot.core.plot.base.GeomKind import org.jetbrains.letsPlot.core.plot.base.tooltip.GeomTargetCollector import org.jetbrains.letsPlot.core.plot.base.tooltip.GeomTargetCollector.TooltipParams import org.jetbrains.letsPlot.core.plot.base.tooltip.TipLayoutHint @@ -17,10 +16,9 @@ import org.jetbrains.letsPlot.core.plot.base.tooltip.TipLayoutHint.Kind.HORIZONT import org.jetbrains.letsPlot.core.plot.base.tooltip.TipLayoutHint.Kind.VERTICAL_TOOLTIP class TargetCollectorHelper( - geomKind: GeomKind, private val ctx: GeomContext ) { - private val colorMarkerMapper = HintColorUtil.createColorMarkerMapper(geomKind, ctx) + private val colorMarkerMapper = HintColorUtil.createColorMarkerMapper(ctx) private val targetCollector: GeomTargetCollector = ctx.targetCollector fun addPaths(paths: Collection) { diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/util/TextHelper.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/util/TextHelper.kt index 1e5d0ef0798..1bf9e1ec446 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/util/TextHelper.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/util/TextHelper.kt @@ -104,7 +104,7 @@ class TextHelper( } internal fun buildHints(targetCollector: GeomTargetCollector) { - val colorsByDataPoint = HintColorUtil.createColorMarkerMapper(GeomKind.TEXT, this.ctx) + val colorsByDataPoint = HintColorUtil.createColorMarkerMapper(this.ctx) myAesthetics.dataPoints().forEach { p -> val point = toLocation(p) ?: return diff --git a/plot-base/src/commonTest/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/AreaGeomTest.kt b/plot-base/src/commonTest/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/AreaGeomTest.kt index 151770fe9e4..522002bfdbf 100644 --- a/plot-base/src/commonTest/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/AreaGeomTest.kt +++ b/plot-base/src/commonTest/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/AreaGeomTest.kt @@ -9,6 +9,7 @@ import org.jetbrains.letsPlot.commons.geometry.DoubleVector import org.jetbrains.letsPlot.core.plot.base.Aes import org.jetbrains.letsPlot.core.plot.base.BogusContext import org.jetbrains.letsPlot.core.plot.base.GeomContext +import org.jetbrains.letsPlot.core.plot.base.GeomKind import org.jetbrains.letsPlot.core.plot.base.aes.AestheticsBuilder import org.jetbrains.letsPlot.core.plot.base.aes.AestheticsBuilder.Companion.list import org.jetbrains.letsPlot.core.plot.base.coord.Coords @@ -51,6 +52,7 @@ class AreaGeomTest { class EmptyGeomContext : GeomContext by BogusContext { override val flipped: Boolean = false override val targetCollector: GeomTargetCollector = NullGeomTargetCollector + override fun geomKind(): GeomKind = GeomKind.AREA } class DummyRoot : SvgRoot { diff --git a/plot-base/src/jvmTest/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/ErrorBarGeomTest.kt b/plot-base/src/jvmTest/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/ErrorBarGeomTest.kt index 96847831f56..37d70e65d35 100644 --- a/plot-base/src/jvmTest/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/ErrorBarGeomTest.kt +++ b/plot-base/src/jvmTest/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/ErrorBarGeomTest.kt @@ -15,6 +15,7 @@ import org.jetbrains.letsPlot.commons.intern.spatial.projections.mercator import org.jetbrains.letsPlot.core.plot.base.Aes import org.jetbrains.letsPlot.core.plot.base.BogusContext import org.jetbrains.letsPlot.core.plot.base.GeomContext +import org.jetbrains.letsPlot.core.plot.base.GeomKind import org.jetbrains.letsPlot.core.plot.base.aes.AestheticsBuilder import org.jetbrains.letsPlot.core.plot.base.aes.AestheticsBuilder.Companion.list import org.jetbrains.letsPlot.core.plot.base.coord.CoordinatesMapper @@ -90,6 +91,7 @@ class ErrorBarGeomTest { override fun getResolution(aes: Aes): Double = 10.0 override fun isMappedAes(aes: Aes<*>) = aes == Aes.X || aes == Aes.YMIN || aes == Aes.YMAX override fun consumeMessages(messages: List) {} + override fun geomKind(): GeomKind = GeomKind.ERROR_BAR }, pos = PositionAdjustments.identity(), ) @@ -169,6 +171,7 @@ class ErrorBarGeomTest { override val targetCollector: GeomTargetCollector = NullGeomTargetCollector override fun getResolution(aes: Aes): Double = 10.0 override fun isMappedAes(aes: Aes<*>) = aes == Aes.X || aes == Aes.YMIN || aes == Aes.YMAX + override fun geomKind() = GeomKind.ERROR_BAR }, pos = PositionAdjustments.identity(), ) diff --git a/plot-base/src/jvmTest/kotlin/org/jetbrains/letsPlot/core/plot/base/tooltip/loc/PolygonEdgeCasesTest.kt b/plot-base/src/jvmTest/kotlin/org/jetbrains/letsPlot/core/plot/base/tooltip/loc/PolygonEdgeCasesTest.kt index a07521d1bdc..cbd2820a20f 100644 --- a/plot-base/src/jvmTest/kotlin/org/jetbrains/letsPlot/core/plot/base/tooltip/loc/PolygonEdgeCasesTest.kt +++ b/plot-base/src/jvmTest/kotlin/org/jetbrains/letsPlot/core/plot/base/tooltip/loc/PolygonEdgeCasesTest.kt @@ -11,9 +11,13 @@ import org.jetbrains.letsPlot.commons.intern.typedGeometry.Vec import org.jetbrains.letsPlot.commons.intern.typedGeometry.algorithms.splitRings import org.jetbrains.letsPlot.commons.intern.typedGeometry.createMultiPolygon import org.jetbrains.letsPlot.commons.intern.typedGeometry.explicitVec +import org.jetbrains.letsPlot.core.plot.base.BogusContext +import org.jetbrains.letsPlot.core.plot.base.BogusCoordinateSystem import org.jetbrains.letsPlot.core.plot.base.aes.AestheticsBuilder import org.jetbrains.letsPlot.core.plot.base.aes.AestheticsBuilder.Companion.list import org.jetbrains.letsPlot.core.plot.base.geom.util.GeomUtil +import org.jetbrains.letsPlot.core.plot.base.geom.util.LinesHelper +import org.jetbrains.letsPlot.core.plot.base.pos.PositionAdjustments import org.jetbrains.letsPlot.core.plot.base.tooltip.GeomTargetLocator import org.jetbrains.letsPlot.core.plot.base.tooltip.GeomTargetLocator.LookupSpace import org.jetbrains.letsPlot.core.plot.base.tooltip.GeomTargetLocator.LookupStrategy @@ -163,7 +167,9 @@ class PolygonEdgeCasesTest { .y(list(polygon.map(DoubleVector::y))) .build() - val pathData = GeomUtil.createPaths(aes.dataPoints(), GeomUtil.TO_LOCATION_X_Y, sorted = true) {} + val linesHelper = LinesHelper(PositionAdjustments.identity(), BogusCoordinateSystem, BogusContext) + + val pathData = linesHelper.createPaths(aes.dataPoints(), GeomUtil.TO_LOCATION_X_Y, sorted = true) val rings = splitRings(pathData[0].coordinates) assertEquals(3, rings.size) diff --git a/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/assemble/GeomContextBuilder.kt b/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/assemble/GeomContextBuilder.kt index 379e1e92edf..0e8402b2706 100644 --- a/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/assemble/GeomContextBuilder.kt +++ b/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/assemble/GeomContextBuilder.kt @@ -31,22 +31,23 @@ class GeomContextBuilder : ImmutableGeomContext.Builder { private var coordinateSystem: CoordinateSystem? = null private var contentBounds: DoubleRectangle? = null private var scaleFactor: Double = 1.0 + private var geomKind: GeomKind? = null private var messageConsumer: (String) -> Unit = {} constructor() - private constructor(ctx: MyGeomContext) { - flipped = ctx.flipped - aesthetics = ctx.aesthetics - aestheticMappers = ctx.aestheticMappers - aesBounds = ctx._aesBounds - geomTargetCollector = ctx.targetCollector - annotation = ctx.annotation - defaultFormatters = ctx.defaultFormatters - backgroundColor = ctx.backgroundColor - plotContext = ctx.plotContext - coordinateSystem = ctx._coordinateSystem - } +// private constructor(ctx: MyGeomContext) { +// flipped = ctx.flipped +// aesthetics = ctx.aesthetics +// aestheticMappers = ctx.aestheticMappers +// aesBounds = ctx._aesBounds +// geomTargetCollector = ctx.targetCollector +// annotation = ctx.annotation +// defaultFormatters = ctx.defaultFormatters +// backgroundColor = ctx.backgroundColor +// plotContext = ctx.plotContext +// coordinateSystem = ctx._coordinateSystem +// } override fun flipped(flipped: Boolean): ImmutableGeomContext.Builder { this.flipped = flipped @@ -118,6 +119,11 @@ class GeomContextBuilder : ImmutableGeomContext.Builder { return this } + override fun geomKind(geomKind: GeomKind): ImmutableGeomContext.Builder { + this.geomKind = geomKind + return this + } + override fun build(): ImmutableGeomContext { return MyGeomContext(this) } @@ -132,6 +138,7 @@ class GeomContextBuilder : ImmutableGeomContext.Builder { val _contentBounds = b.contentBounds val _scaleFactor = b.scaleFactor val _messageConsumer = b.messageConsumer + val _geomKind = b.geomKind override val flipped: Boolean = b.flipped override val targetCollector = b.geomTargetCollector @@ -207,14 +214,19 @@ class GeomContextBuilder : ImmutableGeomContext.Builder { messages.forEach { _messageConsumer(it) } } - override fun withTargetCollector(targetCollector: GeomTargetCollector): GeomContext { - return with() - .geomTargetCollector(targetCollector) - .build() + override fun geomKind(): GeomKind { + return _geomKind ?: error("GeomContext: geom kind is not defined.") } - override fun with(): ImmutableGeomContext.Builder { - return GeomContextBuilder(this) - } +// not used +// override fun withTargetCollector(targetCollector: GeomTargetCollector): GeomContext { +// return with() +// .geomTargetCollector(targetCollector) +// .build() +// } +// +// override fun with(): ImmutableGeomContext.Builder { +// return GeomContextBuilder(this) +// } } } diff --git a/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/assemble/ImmutableGeomContext.kt b/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/assemble/ImmutableGeomContext.kt index 9b1cf56d8c8..cc950d42531 100644 --- a/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/assemble/ImmutableGeomContext.kt +++ b/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/assemble/ImmutableGeomContext.kt @@ -13,8 +13,8 @@ import org.jetbrains.letsPlot.core.plot.base.theme.FontFamilyRegistry import org.jetbrains.letsPlot.core.plot.base.tooltip.GeomTargetCollector interface ImmutableGeomContext : GeomContext { - - fun with(): Builder +// not used +// fun with(): Builder interface Builder { fun flipped(flipped: Boolean): Builder @@ -43,6 +43,8 @@ interface ImmutableGeomContext : GeomContext { fun scaleFactor(scaleFactor: Double): Builder + fun geomKind(geomKind: GeomKind): Builder + fun messageConsumer(messageConsumer: (String) -> Unit): Builder fun build(): ImmutableGeomContext diff --git a/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/frame/FrameOfReferenceBase.kt b/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/frame/FrameOfReferenceBase.kt index e53aaa48e0e..29d81b0c00e 100644 --- a/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/frame/FrameOfReferenceBase.kt +++ b/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/frame/FrameOfReferenceBase.kt @@ -211,6 +211,7 @@ internal abstract class FrameOfReferenceBase( .coordinateSystem(coord) .contentBounds(bounds) .scaleFactor(plotContext.getScaleFactor()) + .geomKind(layer.geomKind) .messageConsumer(plotContext.getMessageConsumer()) .build() diff --git a/plot-livemap/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/livemap/DataPointsConverter.kt b/plot-livemap/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/livemap/DataPointsConverter.kt index c88e575c134..1a7c980ab76 100644 --- a/plot-livemap/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/livemap/DataPointsConverter.kt +++ b/plot-livemap/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/livemap/DataPointsConverter.kt @@ -12,18 +12,15 @@ import org.jetbrains.letsPlot.commons.intern.typedGeometry.Vec import org.jetbrains.letsPlot.commons.intern.typedGeometry.explicitVec import org.jetbrains.letsPlot.commons.values.Color import org.jetbrains.letsPlot.core.commons.data.SeriesUtil -import org.jetbrains.letsPlot.core.plot.base.Aes -import org.jetbrains.letsPlot.core.plot.base.Aesthetics -import org.jetbrains.letsPlot.core.plot.base.DataPointAesthetics -import org.jetbrains.letsPlot.core.plot.base.Geom +import org.jetbrains.letsPlot.core.plot.base.* import org.jetbrains.letsPlot.core.plot.base.geom.* import org.jetbrains.letsPlot.core.plot.base.geom.util.* import org.jetbrains.letsPlot.core.plot.base.geom.util.GeomUtil.TO_LOCATION_X_Y import org.jetbrains.letsPlot.core.plot.base.geom.util.GeomUtil.TO_RECTANGLE -import org.jetbrains.letsPlot.core.plot.base.geom.util.GeomUtil.createPaths import org.jetbrains.letsPlot.core.plot.base.geom.util.GeomUtil.toLocation import org.jetbrains.letsPlot.core.plot.base.geom.util.LinesHelper.Companion.midPointsPathInterpolator import org.jetbrains.letsPlot.core.plot.base.geom.util.LinesHelper.Companion.splitByStyle +import org.jetbrains.letsPlot.core.plot.base.pos.PositionAdjustments import org.jetbrains.letsPlot.core.plot.builder.scale.DefaultNaValue import org.jetbrains.letsPlot.livemap.Client import org.jetbrains.letsPlot.livemap.Client.Companion.px @@ -150,10 +147,12 @@ internal class DataPointsConverter( } } - private inner class MultiPathFeatureConverter( + private class MultiPathFeatureConverter( aes: Aesthetics ) : PathFeatureConverterBase(aes) { + private val linesHelper = LinesHelper(PositionAdjustments.identity(), BogusCoordinateSystem, BogusContext) + fun path(geom: Geom): List { if (geom is PathGeom) { setAnimation(geom.animation) @@ -161,7 +160,7 @@ internal class DataPointsConverter( setGeodesic(geom.geodesic) } - val paths = createPaths(aesthetics.dataPoints(), TO_LOCATION_X_Y, sorted = true) {} + val paths = linesHelper.createPaths(aesthetics.dataPoints(), TO_LOCATION_X_Y, sorted = true) val interpolatedPathData = paths.flatMap { splitByStyle(it).let(::midPointsPathInterpolator) @@ -171,7 +170,7 @@ internal class DataPointsConverter( } fun polygon(): List { - val paths = createPaths(aesthetics.dataPoints(), TO_LOCATION_X_Y, sorted = true) {} + val paths = linesHelper.createPaths(aesthetics.dataPoints(), TO_LOCATION_X_Y, sorted = true) return process(paths = paths, isClosed = true) } @@ -185,7 +184,7 @@ internal class DataPointsConverter( } } - private inner class SinglePathFeatureConverter( + private class SinglePathFeatureConverter( aesthetics: Aesthetics ) : PathFeatureConverterBase(aesthetics) { fun tile(): List { From 94c54afb9ba690efb34ae3c8ec86532a11f1b321 Mon Sep 17 00:00:00 2001 From: Ivan Seleznev Date: Thu, 19 Mar 2026 11:20:29 +0100 Subject: [PATCH 02/10] Fix the counting of dropped points --- .../core/plot/base/geom/util/LinesHelper.kt | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/util/LinesHelper.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/util/LinesHelper.kt index 5bcdf12d59f..a190348456a 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/util/LinesHelper.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/util/LinesHelper.kt @@ -27,7 +27,7 @@ open class LinesHelper( ctx: GeomContext ) : GeomHelper(pos, coord, ctx) { - private var myDroppedPointsCount = 0 + private val myDroppedPointsIds = mutableSetOf() private var myAlphaEnabled = true protected var myResamplingEnabled = false protected var myResamplingPrecision = PIXEL_PRECISION @@ -106,7 +106,7 @@ open class LinesHelper( } fun getDroppedPointsCount(): Int { - return myDroppedPointsCount + return myDroppedPointsIds.size } private fun createPolygon(domainPathData: Collection): List> { @@ -202,20 +202,14 @@ open class LinesHelper( } } - var nulls = 0 - var singlePointPaths = 0 val result = groups.values .map { aesthetics -> toPathPoints(aesthetics, pointTransform) } - .also { a -> nulls += a.flatten().count { it == null } } - .map { pathPoints -> pathPoints.splitByNull() } - .flatten() + .flatMap { pathPoints -> pathPoints.splitByNull() } .mapNotNull { - if (it.size == 1) singlePointPaths++ + if (it.size == 1) myDroppedPointsIds.add(it[0].aes.index()) PathData.create(it) } - myDroppedPointsCount += nulls + singlePointPaths - return result } @@ -235,7 +229,13 @@ open class LinesHelper( pointTransform: ((DataPointAesthetics) -> DoubleVector?) ): List { return dataPoints.map { aes -> - pointTransform(aes)?.let { p -> PathPoint(aes, p) } + val p = pointTransform(aes) + if (p == null) { + myDroppedPointsIds.add(aes.index()) + null + } else { + PathPoint(aes, p) + } } } From 2cffe733c11efae5cf2cb9dd5657e005fe0b4f24 Mon Sep 17 00:00:00 2001 From: Ivan Seleznev Date: Thu, 19 Mar 2026 11:49:16 +0100 Subject: [PATCH 03/10] Clean up commented code in geoms --- .../letsPlot/core/plot/base/geom/ContourGeom.kt | 5 +---- .../letsPlot/core/plot/base/geom/ContourfGeom.kt | 6 +----- .../letsPlot/core/plot/base/geom/CrossBarGeom.kt | 16 +--------------- .../core/plot/base/geom/Density2dGeom.kt | 2 -- .../core/plot/base/geom/Density2dfGeom.kt | 2 -- .../letsPlot/core/plot/base/geom/DotplotGeom.kt | 1 - .../letsPlot/core/plot/base/geom/FreqpolyGeom.kt | 5 +---- .../letsPlot/core/plot/base/geom/JitterGeom.kt | 5 +---- .../core/plot/base/geom/LabelRepelGeom.kt | 3 +-- .../letsPlot/core/plot/base/geom/LineGeom.kt | 5 +---- .../letsPlot/core/plot/base/geom/MapGeom.kt | 13 ------------- .../letsPlot/core/plot/base/geom/RasterGeom.kt | 9 --------- .../letsPlot/core/plot/base/geom/YDotplotGeom.kt | 1 - 13 files changed, 7 insertions(+), 66 deletions(-) diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/ContourGeom.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/ContourGeom.kt index 467d1dccb45..a39792b51f9 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/ContourGeom.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/ContourGeom.kt @@ -7,9 +7,6 @@ package org.jetbrains.letsPlot.core.plot.base.geom open class ContourGeom : PathGeom() { companion object { -// val RENDERS: List> = PathGeom.RENDERS - - const val HANDLES_GROUPS = - PathGeom.HANDLES_GROUPS + const val HANDLES_GROUPS = PathGeom.HANDLES_GROUPS } } diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/ContourfGeom.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/ContourfGeom.kt index 0a86ba35392..7357952a9e6 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/ContourfGeom.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/ContourfGeom.kt @@ -7,10 +7,6 @@ package org.jetbrains.letsPlot.core.plot.base.geom open class ContourfGeom : PolygonGeom() { companion object { - -// val RENDERS = PolygonGeom.RENDERS - - const val HANDLES_GROUPS = - PolygonGeom.HANDLES_GROUPS + const val HANDLES_GROUPS = PolygonGeom.HANDLES_GROUPS } } diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/CrossBarGeom.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/CrossBarGeom.kt index ee0a2f068f2..de4758adbc8 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/CrossBarGeom.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/CrossBarGeom.kt @@ -10,13 +10,8 @@ import org.jetbrains.letsPlot.commons.geometry.DoubleSegment import org.jetbrains.letsPlot.commons.geometry.DoubleVector import org.jetbrains.letsPlot.commons.interval.DoubleSpan import org.jetbrains.letsPlot.core.plot.base.* -import org.jetbrains.letsPlot.core.plot.base.geom.annotation.BarAnnotation import org.jetbrains.letsPlot.core.plot.base.geom.annotation.CrossBarAnnotation -import org.jetbrains.letsPlot.core.plot.base.geom.util.BoxHelper -import org.jetbrains.letsPlot.core.plot.base.geom.util.GeomHelper -import org.jetbrains.letsPlot.core.plot.base.geom.util.HintColorUtil -import org.jetbrains.letsPlot.core.plot.base.geom.util.RectangleTooltipHelper -import org.jetbrains.letsPlot.core.plot.base.geom.util.RectanglesHelper +import org.jetbrains.letsPlot.core.plot.base.geom.util.* import org.jetbrains.letsPlot.core.plot.base.render.LegendKeyElementFactory import org.jetbrains.letsPlot.core.plot.base.render.SvgRoot import org.jetbrains.letsPlot.core.plot.base.tooltip.TipLayoutHint @@ -120,15 +115,6 @@ class CrossBarGeom : GeomBase(), WithWidth { return ::factory } -// private fun clientRectByDataPoint(geomHelper: GeomHelper): (DataPointAesthetics) -> DoubleRectangle? { -// val factory = rectByDataPoint(geomHelper) -// return { p -> -// factory(p)?.let { rect -> -// geomHelper.toClient(rect, p) -// } -// } -// } - companion object { const val HANDLES_GROUPS = false private val DEF_WIDTH_UNIT: DimensionUnit = DimensionUnit.RESOLUTION diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/Density2dGeom.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/Density2dGeom.kt index 98849b663ec..ad497dfbe84 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/Density2dGeom.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/Density2dGeom.kt @@ -7,8 +7,6 @@ package org.jetbrains.letsPlot.core.plot.base.geom class Density2dGeom : ContourGeom() { companion object { -// val RENDERS = ContourGeom.RENDERS - const val HANDLES_GROUPS = ContourGeom.HANDLES_GROUPS } } diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/Density2dfGeom.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/Density2dfGeom.kt index 9d8f78c0f24..c43bd77fbd9 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/Density2dfGeom.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/Density2dfGeom.kt @@ -7,8 +7,6 @@ package org.jetbrains.letsPlot.core.plot.base.geom class Density2dfGeom : ContourfGeom() { companion object { -// val RENDERS: List> = ContourfGeom.RENDERS - const val HANDLES_GROUPS = ContourfGeom.HANDLES_GROUPS } } diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/DotplotGeom.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/DotplotGeom.kt index f0f8cf2c3cf..a61f1d02e26 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/DotplotGeom.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/DotplotGeom.kt @@ -60,7 +60,6 @@ open class DotplotGeom : GeomBase(), WithWidth { ) if (!pointsWithBinWidth.any()) return -// val binWidthPx = pointsWithBinWidth.first().binwidth()!! * ctx.getUnitResolution(Aes.X) val binWidthPx = pointsWithBinWidth.first().let { val x = it.x()!! val y = it.y()!! diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/FreqpolyGeom.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/FreqpolyGeom.kt index ec4edd7a205..a5278ce2c3d 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/FreqpolyGeom.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/FreqpolyGeom.kt @@ -7,9 +7,6 @@ package org.jetbrains.letsPlot.core.plot.base.geom class FreqpolyGeom : LineGeom() { companion object { -// val RENDERS: List> = LineGeom.RENDERS - - const val HANDLES_GROUPS = - LineGeom.HANDLES_GROUPS + const val HANDLES_GROUPS = LineGeom.HANDLES_GROUPS } } diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/JitterGeom.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/JitterGeom.kt index 724ad035175..c2acb5d893c 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/JitterGeom.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/JitterGeom.kt @@ -7,9 +7,6 @@ package org.jetbrains.letsPlot.core.plot.base.geom class JitterGeom : PointGeom() { companion object { -// val RENDERS: List> = PointGeom.RENDERS - - const val HANDLES_GROUPS = - PointGeom.HANDLES_GROUPS + const val HANDLES_GROUPS = PointGeom.HANDLES_GROUPS } } diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/LabelRepelGeom.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/LabelRepelGeom.kt index 86656ea74d3..8af4e34c4ec 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/LabelRepelGeom.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/LabelRepelGeom.kt @@ -12,8 +12,7 @@ import org.jetbrains.letsPlot.core.plot.base.PositionAdjustment import org.jetbrains.letsPlot.core.plot.base.geom.util.LabelOptions import org.jetbrains.letsPlot.core.plot.base.geom.util.TextHelper - -class LabelRepelGeom() : TextRepelGeom() { +class LabelRepelGeom : TextRepelGeom() { val labelOptions = LabelOptions() override fun getTextHelper( diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/LineGeom.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/LineGeom.kt index d89ba3fa4df..6db94ecd6be 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/LineGeom.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/LineGeom.kt @@ -16,9 +16,6 @@ open class LineGeom : PathGeom() { } companion object { -// val RENDERS = PathGeom.RENDERS - - const val HANDLES_GROUPS = - PathGeom.HANDLES_GROUPS + const val HANDLES_GROUPS = PathGeom.HANDLES_GROUPS } } diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/MapGeom.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/MapGeom.kt index 610d32d2744..5839b8e9311 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/MapGeom.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/MapGeom.kt @@ -7,19 +7,6 @@ package org.jetbrains.letsPlot.core.plot.base.geom class MapGeom : PolygonGeom() { companion object { -// val RENDERS = listOf( -// -// // auto-wired to 'x' or 'long' and to 'y' or 'lat' -// Aes.X, -// Aes.Y, -// -// Aes.SIZE, // path width -// Aes.LINETYPE, -// Aes.COLOR, -// Aes.FILL, -// Aes.ALPHA, -// ) - const val HANDLES_GROUPS = true } } diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/RasterGeom.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/RasterGeom.kt index 083db06603d..f620ea50dca 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/RasterGeom.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/RasterGeom.kt @@ -115,15 +115,6 @@ class RasterGeom : GeomBase() { } companion object { -// val RENDERS = listOf( -// Aes.X, -// Aes.Y, -// Aes.WIDTH, // not rendered but required for correct x aes range computation -// Aes.HEIGHT, // -- the same -- -// Aes.FILL, -// Aes.ALPHA -// ) - const val HANDLES_GROUPS = false } }// ToDo: hjust, vjust [0..1] def .5 diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/YDotplotGeom.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/YDotplotGeom.kt index b0e1e5dbee8..865328a6b22 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/YDotplotGeom.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/YDotplotGeom.kt @@ -37,7 +37,6 @@ class YDotplotGeom : DotplotGeom(), WithHeight { ) if (!pointsWithBinWidth.any()) return -// val binWidthPx = pointsWithBinWidth.first().binwidth()!! * ctx.getUnitResolution(Aes.Y) val binWidthPx = pointsWithBinWidth.first().let { val x = it.x()!! val y = it.y()!! From 7906bede7b755cc0c20d7497cadf2d77f36e0116 Mon Sep 17 00:00:00 2001 From: Ivan Seleznev Date: Fri, 20 Mar 2026 13:58:28 +0100 Subject: [PATCH 04/10] Add na_rm --- .../plot/common/model/EmptyGeomContext.kt | 2 + .../letsPlot/core/plot/base/BogusContext.kt | 2 + .../letsPlot/core/plot/base/GeomContext.kt | 2 + .../letsPlot/core/plot/base/geom/GeomBase.kt | 2 +- .../letsPlot/core/plot/builder/GeomLayer.kt | 2 + .../builder/assemble/GeomContextBuilder.kt | 11 ++++ .../plot/builder/assemble/GeomLayerBuilder.kt | 5 +- .../builder/assemble/ImmutableGeomContext.kt | 2 + .../builder/frame/FrameOfReferenceBase.kt | 1 + .../jetbrains/letsPlot/core/spec/GeomProto.kt | 62 ++++++++++--------- .../jetbrains/letsPlot/core/spec/Option.kt | 2 +- .../core/spec/front/tiles/PlotTilesConfig.kt | 5 +- 12 files changed, 64 insertions(+), 34 deletions(-) diff --git a/demo/common-plot/src/commonMain/kotlin/demo/plot/common/model/EmptyGeomContext.kt b/demo/common-plot/src/commonMain/kotlin/demo/plot/common/model/EmptyGeomContext.kt index 7e499e4f756..80dd33752a5 100644 --- a/demo/common-plot/src/commonMain/kotlin/demo/plot/common/model/EmptyGeomContext.kt +++ b/demo/common-plot/src/commonMain/kotlin/demo/plot/common/model/EmptyGeomContext.kt @@ -54,6 +54,8 @@ class EmptyGeomContext : GeomContext { return 1.0 } + override fun removeNaMessages() = true + override fun consumeMessages(messages: List) { throw IllegalStateException("Not available in an empty geom context") } diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/BogusContext.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/BogusContext.kt index 3d52af4be40..219ab86b7b1 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/BogusContext.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/BogusContext.kt @@ -55,6 +55,8 @@ object BogusContext : GeomContext { return 1.0 } + override fun removeNaMessages() = true + override fun consumeMessages(messages: List) { // do nothing } diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/GeomContext.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/GeomContext.kt index af82a543c67..48a72519158 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/GeomContext.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/GeomContext.kt @@ -53,6 +53,8 @@ interface GeomContext { fun getScaleFactor(): Double + fun removeNaMessages(): Boolean + fun consumeMessages(messages: List) fun geomKind(): GeomKind diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/GeomBase.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/GeomBase.kt index 43b0d1102cc..5f53b96a776 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/GeomBase.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/GeomBase.kt @@ -46,7 +46,7 @@ abstract class GeomBase : Geom { } fun reportDroppedPoints(count: Int, ctx: GeomContext) { - if (SHOW_NA_MESSAGES && count > 0) { + if (!ctx.removeNaMessages() && count > 0) { ctx.consumeMessages(listOf("${ctx.geomKind().name.lowercase()}: removed $count data point(s)")) } } diff --git a/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/GeomLayer.kt b/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/GeomLayer.kt index a4d732ea2ad..9b728c3ea21 100644 --- a/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/GeomLayer.kt +++ b/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/GeomLayer.kt @@ -61,6 +61,8 @@ interface GeomLayer { val defaultFormatters: Map String> + val naRm: Boolean + fun renderedAes(considerOrientation: Boolean = false): List> fun hasBinding(aes: Aes<*>): Boolean diff --git a/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/assemble/GeomContextBuilder.kt b/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/assemble/GeomContextBuilder.kt index 0e8402b2706..bb68ac99424 100644 --- a/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/assemble/GeomContextBuilder.kt +++ b/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/assemble/GeomContextBuilder.kt @@ -32,6 +32,7 @@ class GeomContextBuilder : ImmutableGeomContext.Builder { private var contentBounds: DoubleRectangle? = null private var scaleFactor: Double = 1.0 private var geomKind: GeomKind? = null + private var naRm: Boolean = false private var messageConsumer: (String) -> Unit = {} constructor() @@ -114,6 +115,11 @@ class GeomContextBuilder : ImmutableGeomContext.Builder { return this } + override fun naRm(naRm: Boolean): ImmutableGeomContext.Builder { + this.naRm = naRm + return this + } + override fun messageConsumer(messageConsumer: (String) -> Unit): ImmutableGeomContext.Builder { this.messageConsumer = messageConsumer return this @@ -137,6 +143,7 @@ class GeomContextBuilder : ImmutableGeomContext.Builder { val _coordinateSystem = b.coordinateSystem val _contentBounds = b.contentBounds val _scaleFactor = b.scaleFactor + val _naRm = b.naRm val _messageConsumer = b.messageConsumer val _geomKind = b.geomKind @@ -210,6 +217,10 @@ class GeomContextBuilder : ImmutableGeomContext.Builder { return _scaleFactor } + override fun removeNaMessages(): Boolean { + return _naRm + } + override fun consumeMessages(messages: List) { messages.forEach { _messageConsumer(it) } } diff --git a/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/assemble/GeomLayerBuilder.kt b/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/assemble/GeomLayerBuilder.kt index c16c65ce201..f253e7cb811 100644 --- a/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/assemble/GeomLayerBuilder.kt +++ b/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/assemble/GeomLayerBuilder.kt @@ -178,6 +178,7 @@ class GeomLayerBuilder( data: DataFrame, scaleMap: Map, Scale>, scaleMapppersNP: Map, ScaleMapper<*>>, + naRm: Boolean = false, ): GeomLayer { val transformByAes: Map, Transform> = scaleMap.keys.associateWith { scaleMap.getValue(it).transform @@ -252,6 +253,7 @@ class GeomLayerBuilder( fillByAes = fillByAes, annotationProvider = myAnnotationProvider, defaultFormatters = myDefaultFormatters, + naRm = naRm, ) } @@ -282,7 +284,8 @@ class GeomLayerBuilder( override val colorByAes: Aes, override val fillByAes: Aes, private val annotationProvider: ((MappedDataAccess, DataFrame) -> Annotation?)?, - override val defaultFormatters: Map String> + override val defaultFormatters: Map String>, + override val naRm: Boolean ) : GeomLayer { override val geom: Geom = geomProvider.createGeom( diff --git a/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/assemble/ImmutableGeomContext.kt b/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/assemble/ImmutableGeomContext.kt index cc950d42531..7785114da23 100644 --- a/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/assemble/ImmutableGeomContext.kt +++ b/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/assemble/ImmutableGeomContext.kt @@ -45,6 +45,8 @@ interface ImmutableGeomContext : GeomContext { fun geomKind(geomKind: GeomKind): Builder + fun naRm(naRm: Boolean): Builder + fun messageConsumer(messageConsumer: (String) -> Unit): Builder fun build(): ImmutableGeomContext diff --git a/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/frame/FrameOfReferenceBase.kt b/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/frame/FrameOfReferenceBase.kt index 29d81b0c00e..aa9aa511829 100644 --- a/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/frame/FrameOfReferenceBase.kt +++ b/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/frame/FrameOfReferenceBase.kt @@ -211,6 +211,7 @@ internal abstract class FrameOfReferenceBase( .coordinateSystem(coord) .contentBounds(bounds) .scaleFactor(plotContext.getScaleFactor()) + .naRm(layer.naRm) .geomKind(layer.geomKind) .messageConsumer(plotContext.getMessageConsumer()) .build() diff --git a/plot-stem/src/commonMain/kotlin/org/jetbrains/letsPlot/core/spec/GeomProto.kt b/plot-stem/src/commonMain/kotlin/org/jetbrains/letsPlot/core/spec/GeomProto.kt index 01be6c956c7..700b6173758 100644 --- a/plot-stem/src/commonMain/kotlin/org/jetbrains/letsPlot/core/spec/GeomProto.kt +++ b/plot-stem/src/commonMain/kotlin/org/jetbrains/letsPlot/core/spec/GeomProto.kt @@ -191,45 +191,47 @@ class GeomProto(val geomKind: GeomKind) { private companion object { private val DEFAULTS = HashMap>() - private val COMMON = commonDefaults() init { + val defaultsByGeom = HashMap>() + defaultsByGeom[SMOOTH] = smoothDefaults() + defaultsByGeom[BAR] = barDefaults() + defaultsByGeom[HISTOGRAM] = histogramDefaults() + defaultsByGeom[DOT_PLOT] = dotplotDefaults() + defaultsByGeom[CONTOUR] = contourDefaults() + defaultsByGeom[CONTOURF] = contourfDefaults() + defaultsByGeom[CROSS_BAR] = crossBarDefaults() + defaultsByGeom[BOX_PLOT] = boxplotDefaults() + defaultsByGeom[AREA_RIDGES] = areaRidgesDefaults() + defaultsByGeom[VIOLIN] = violinDefaults() + defaultsByGeom[SINA] = sinaDefaults() + defaultsByGeom[Y_DOT_PLOT] = yDotplotDefaults() + defaultsByGeom[AREA] = areaDefaults() + defaultsByGeom[DENSITY] = densityDefaults() + defaultsByGeom[DENSITY2D] = density2dDefaults() + defaultsByGeom[DENSITY2DF] = density2dfDefaults() + defaultsByGeom[POINT_DENSITY] = pointDensityDefaults() + defaultsByGeom[Q_Q] = qqDefaults() + defaultsByGeom[Q_Q_2] = qq2Defaults() + defaultsByGeom[Q_Q_LINE] = qqLineDefaults() + defaultsByGeom[Q_Q_2_LINE] = qq2LineDefaults() + defaultsByGeom[FREQPOLY] = freqpolyDefaults() + defaultsByGeom[BIN_2D] = bin2dDefaults() + defaultsByGeom[HEX] = hexDefaults() + defaultsByGeom[PIE] = pieDefaults() + defaultsByGeom[BRACKET] = bracketDefaults() + defaultsByGeom[BRACKET_DODGE] = bracketDefaults() + + val commonDefaults = commonDefaults() for (geomKind in GeomKind.entries) { - DEFAULTS[geomKind] = COMMON + DEFAULTS[geomKind] = commonDefaults + (defaultsByGeom[geomKind] ?: emptyMap()) } - - DEFAULTS[SMOOTH] = smoothDefaults() - DEFAULTS[BAR] = barDefaults() - DEFAULTS[HISTOGRAM] = histogramDefaults() - DEFAULTS[DOT_PLOT] = dotplotDefaults() - DEFAULTS[CONTOUR] = contourDefaults() - DEFAULTS[CONTOURF] = contourfDefaults() - DEFAULTS[CROSS_BAR] = crossBarDefaults() - DEFAULTS[BOX_PLOT] = boxplotDefaults() - DEFAULTS[AREA_RIDGES] = areaRidgesDefaults() - DEFAULTS[VIOLIN] = violinDefaults() - DEFAULTS[SINA] = sinaDefaults() - DEFAULTS[Y_DOT_PLOT] = yDotplotDefaults() - DEFAULTS[AREA] = areaDefaults() - DEFAULTS[DENSITY] = densityDefaults() - DEFAULTS[DENSITY2D] = density2dDefaults() - DEFAULTS[DENSITY2DF] = density2dfDefaults() - DEFAULTS[POINT_DENSITY] = pointDensityDefaults() - DEFAULTS[Q_Q] = qqDefaults() - DEFAULTS[Q_Q_2] = qq2Defaults() - DEFAULTS[Q_Q_LINE] = qqLineDefaults() - DEFAULTS[Q_Q_2_LINE] = qq2LineDefaults() - DEFAULTS[FREQPOLY] = freqpolyDefaults() - DEFAULTS[BIN_2D] = bin2dDefaults() - DEFAULTS[HEX] = hexDefaults() - DEFAULTS[PIE] = pieDefaults() - DEFAULTS[BRACKET] = bracketDefaults() - DEFAULTS[BRACKET_DODGE] = bracketDefaults() } private fun commonDefaults(): Map { val defaults = HashMap() defaults[Layer.STAT] = "identity" + defaults[Layer.NA_RM] = false // hide NA messages by default return defaults } diff --git a/plot-stem/src/commonMain/kotlin/org/jetbrains/letsPlot/core/spec/Option.kt b/plot-stem/src/commonMain/kotlin/org/jetbrains/letsPlot/core/spec/Option.kt index 78329be3787..4adcd1c75d3 100644 --- a/plot-stem/src/commonMain/kotlin/org/jetbrains/letsPlot/core/spec/Option.kt +++ b/plot-stem/src/commonMain/kotlin/org/jetbrains/letsPlot/core/spec/Option.kt @@ -230,7 +230,7 @@ object Option { const val COLOR_BY = "color_by" const val FILL_BY = "fill_by" - const val SCALE_FACTOR = "scale_factor" + const val NA_RM = "na_rm" object Marginal { const val SIZE = "margin_size" diff --git a/plot-stem/src/commonMain/kotlin/org/jetbrains/letsPlot/core/spec/front/tiles/PlotTilesConfig.kt b/plot-stem/src/commonMain/kotlin/org/jetbrains/letsPlot/core/spec/front/tiles/PlotTilesConfig.kt index 5cad4009010..d93f133527c 100644 --- a/plot-stem/src/commonMain/kotlin/org/jetbrains/letsPlot/core/spec/front/tiles/PlotTilesConfig.kt +++ b/plot-stem/src/commonMain/kotlin/org/jetbrains/letsPlot/core/spec/front/tiles/PlotTilesConfig.kt @@ -15,6 +15,7 @@ import org.jetbrains.letsPlot.core.plot.builder.assemble.PlotGeomTiles import org.jetbrains.letsPlot.core.plot.builder.assemble.tiles.FacetedPlotGeomTiles import org.jetbrains.letsPlot.core.plot.builder.assemble.tiles.SimplePlotGeomTiles import org.jetbrains.letsPlot.core.plot.builder.coord.CoordProvider +import org.jetbrains.letsPlot.core.spec.Option.Layer.NA_RM import org.jetbrains.letsPlot.core.spec.PlotConfigUtil import org.jetbrains.letsPlot.core.spec.config.LayerConfig import org.jetbrains.letsPlot.core.spec.config.PlotConfigTransforms @@ -104,7 +105,8 @@ internal object PlotTilesConfig { layerBuilder.build( layerConfigs[layerIndex].combinedData, scalesByLayerBeforeFacets[layerIndex], - mappersNP + mappersNP, + layerConfigs[layerIndex].getBoolean(NA_RM) ) } @@ -184,6 +186,7 @@ internal object PlotTilesConfig { layerData, tileLayerScales, mappersByAesNP, + layerConfigs[layerIndex].getBoolean(NA_RM) ) } From a28a879a581bad16e2d8a2758af4066885baa2bc Mon Sep 17 00:00:00 2001 From: Ivan Seleznev Date: Tue, 24 Mar 2026 15:03:11 +0100 Subject: [PATCH 05/10] Refactor dropped points handling to use excluded indices for accurate reporting --- .../org/jetbrains/letsPlot/core/plot/base/geom/AreaGeom.kt | 4 ++-- .../org/jetbrains/letsPlot/core/plot/base/geom/GeomBase.kt | 4 ++++ .../org/jetbrains/letsPlot/core/plot/base/geom/PathGeom.kt | 4 ++-- .../org/jetbrains/letsPlot/core/plot/base/geom/PolygonGeom.kt | 4 ++-- .../org/jetbrains/letsPlot/core/plot/base/geom/RibbonGeom.kt | 4 ++-- .../org/jetbrains/letsPlot/core/plot/base/geom/StepGeom.kt | 4 ++-- .../letsPlot/core/plot/base/geom/util/LinesHelper.kt | 4 ++-- 7 files changed, 16 insertions(+), 12 deletions(-) diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/AreaGeom.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/AreaGeom.kt index 7933971981f..de2d8892929 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/AreaGeom.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/AreaGeom.kt @@ -49,7 +49,7 @@ open class AreaGeom : GeomBase() { val source = aesthetics.dataPoints() val dataPoints = filterDataPoints(source) - val filteredPointsCount = source.count() - dataPoints.count() + val filteredPointsIds = source.excludedIndicesComparedTo(dataPoints) val closePath = linesHelper.meetsRadarPlotReq() dataPoints.sortedByDescending(DataPointAesthetics::group).groupBy(DataPointAesthetics::group) @@ -75,7 +75,7 @@ open class AreaGeom : GeomBase() { createQuantileLines(groupDataPoints, quantilesHelper).forEach(root::add) } } - reportDroppedPoints(filteredPointsCount + linesHelper.getDroppedPointsCount(), ctx) + reportDroppedPoints((filteredPointsIds + linesHelper.getDroppedPointsIds()).size, ctx) } private fun createQuantileLines( diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/GeomBase.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/GeomBase.kt index 5f53b96a776..e61dcec9b2f 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/GeomBase.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/GeomBase.kt @@ -95,5 +95,9 @@ abstract class GeomBase : Geom { add(path.rootGroup) } } + + fun Iterable.excludedIndicesComparedTo(other: Iterable): Set { + return mapTo(HashSet()) { it.index() } - other.mapTo(HashSet()) { it.index() } + } } } diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/PathGeom.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/PathGeom.kt index 3f29e790820..4f25acd5fc5 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/PathGeom.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/PathGeom.kt @@ -33,7 +33,7 @@ open class PathGeom : GeomBase() { ) { val source = aesthetics.dataPoints() val dataPoints = filterDataPoints(source) - val filteredPointsCount = source.count() - dataPoints.count() + val filteredPointsIds = source.excludedIndicesComparedTo(dataPoints) val linesHelper = LinesHelper(pos, coord, ctx) linesHelper.setResamplingEnabled(!coord.isLinear && !flat) @@ -46,7 +46,7 @@ open class PathGeom : GeomBase() { val svgPath = linesHelper.renderPaths(pathData, filled = false) root.appendNodes(svgPath) - reportDroppedPoints(filteredPointsCount + linesHelper.getDroppedPointsCount(), ctx) + reportDroppedPoints((filteredPointsIds + linesHelper.getDroppedPointsIds()).size, ctx) } companion object { diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/PolygonGeom.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/PolygonGeom.kt index f1160fc80ef..6381f9bceb8 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/PolygonGeom.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/PolygonGeom.kt @@ -26,7 +26,7 @@ open class PolygonGeom : GeomBase() { ) { val source = aesthetics.dataPoints() val dataPoints = filterDataPoints(source) - val filteredPointsCount = source.count() - dataPoints.count() + val filteredPointsIds = source.excludedIndicesComparedTo(dataPoints) val linesHelper = LinesHelper(pos, coord, ctx) linesHelper.setResamplingEnabled(coord.isPolar) @@ -37,7 +37,7 @@ open class PolygonGeom : GeomBase() { targetCollectorHelper.addPolygons(polygonData) root.add(svg) } - reportDroppedPoints(filteredPointsCount + linesHelper.getDroppedPointsCount(), ctx) + reportDroppedPoints((filteredPointsIds + linesHelper.getDroppedPointsIds()).size, ctx) } companion object { diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/RibbonGeom.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/RibbonGeom.kt index b80b7c83011..b389e63bd38 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/RibbonGeom.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/RibbonGeom.kt @@ -35,7 +35,7 @@ class RibbonGeom : GeomBase() { ) { val source = aesthetics.dataPoints() val dataPoints = filterDataPoints(source) - val filteredPointsCount = source.count() - dataPoints.count() + val filteredPointsIds = source.excludedIndicesComparedTo(dataPoints) val linesHelper = LinesHelper(pos, coord, ctx) @@ -50,7 +50,7 @@ class RibbonGeom : GeomBase() { root.appendNodes(linesHelper.createLines(dataPoints, TO_LOCATION_X_YMIN)) buildHints(aesthetics, pos, coord, ctx) - reportDroppedPoints(filteredPointsCount + linesHelper.getDroppedPointsCount(), ctx) + reportDroppedPoints((filteredPointsIds + linesHelper.getDroppedPointsIds()).size, ctx) } private fun buildHints(aesthetics: Aesthetics, pos: PositionAdjustment, coord: CoordinateSystem, ctx: GeomContext) { diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/StepGeom.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/StepGeom.kt index 6b44dee2166..581acf8911b 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/StepGeom.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/StepGeom.kt @@ -41,7 +41,7 @@ class StepGeom : LineGeom() { ) { val source = aesthetics.dataPoints() val dataPoints = filterDataPoints(source) - val filteredPointsCount = source.count() - dataPoints.count() + val filteredPointsIds = source.excludedIndicesComparedTo(dataPoints) val linesHelper = LinesHelper(pos, coord, ctx) @@ -57,7 +57,7 @@ class StepGeom : LineGeom() { val targetCollectorHelper = TargetCollectorHelper(ctx) targetCollectorHelper.addPaths(pathDataList) - reportDroppedPoints(filteredPointsCount + linesHelper.getDroppedPointsCount(), ctx) + reportDroppedPoints((filteredPointsIds + linesHelper.getDroppedPointsIds()).size, ctx) } private fun toLocationFor(viewPort: DoubleRectangle): (DataPointAesthetics) -> DoubleVector? { diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/util/LinesHelper.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/util/LinesHelper.kt index a190348456a..a588e1cf761 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/util/LinesHelper.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/util/LinesHelper.kt @@ -105,8 +105,8 @@ open class LinesHelper( return createPolygon(domainPathData) } - fun getDroppedPointsCount(): Int { - return myDroppedPointsIds.size + fun getDroppedPointsIds(): Set { + return myDroppedPointsIds } private fun createPolygon(domainPathData: Collection): List> { From 264a1392d4b6e63cd3ed0b866bc4455141e7f2f1 Mon Sep 17 00:00:00 2001 From: Ivan Seleznev Date: Tue, 24 Mar 2026 15:49:37 +0100 Subject: [PATCH 06/10] Hide NA messages for point layer of boxplot_geom() --- python-package/lets_plot/plot/geom.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python-package/lets_plot/plot/geom.py b/python-package/lets_plot/plot/geom.py index df01aadcb3a..19458463072 100644 --- a/python-package/lets_plot/plot/geom.py +++ b/python-package/lets_plot/plot/geom.py @@ -3955,7 +3955,8 @@ def geom_boxplot(mapping=None, *, data=None, stat=None, position=None, show_lege shape=outlier_param('shape', outlier_shape), size=size, stroke=outlier_param('stroke', outlier_stroke), - color_by=color_by, fill_by=fill_by) + color_by=color_by, fill_by=fill_by, + na_rm=True) return boxplot_layer From f08968efea5a363549c7a834b4ba790016d42800 Mon Sep 17 00:00:00 2001 From: Ivan Seleznev Date: Wed, 25 Mar 2026 13:47:56 +0100 Subject: [PATCH 07/10] Fix merge --- .../letsPlot/core/plot/builder/assemble/ImmutableGeomContext.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/assemble/ImmutableGeomContext.kt b/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/assemble/ImmutableGeomContext.kt index d6a71830bf8..2ffc0a25790 100644 --- a/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/assemble/ImmutableGeomContext.kt +++ b/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/assemble/ImmutableGeomContext.kt @@ -41,8 +41,6 @@ interface ImmutableGeomContext : GeomContext { fun scaleFactor(scaleFactor: Double): Builder - fun geomKind(geomKind: GeomKind): Builder - fun naRm(naRm: Boolean): Builder fun messageConsumer(messageConsumer: (String) -> Unit): Builder From 4761b4958c0eb72d96029512ba728f31df95c5c5 Mon Sep 17 00:00:00 2001 From: Ivan Seleznev Date: Wed, 25 Mar 2026 16:46:55 +0100 Subject: [PATCH 08/10] Hide NA messages by default --- .../kotlin/org/jetbrains/letsPlot/core/spec/GeomProto.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plot-stem/src/commonMain/kotlin/org/jetbrains/letsPlot/core/spec/GeomProto.kt b/plot-stem/src/commonMain/kotlin/org/jetbrains/letsPlot/core/spec/GeomProto.kt index 700b6173758..34a816c7a76 100644 --- a/plot-stem/src/commonMain/kotlin/org/jetbrains/letsPlot/core/spec/GeomProto.kt +++ b/plot-stem/src/commonMain/kotlin/org/jetbrains/letsPlot/core/spec/GeomProto.kt @@ -231,7 +231,7 @@ class GeomProto(val geomKind: GeomKind) { private fun commonDefaults(): Map { val defaults = HashMap() defaults[Layer.STAT] = "identity" - defaults[Layer.NA_RM] = false // hide NA messages by default + defaults[Layer.NA_RM] = true // hide NA messages by default return defaults } From 5b71ebf5d9512be4ec7f137e112269a61f405f3d Mon Sep 17 00:00:00 2001 From: Ivan Seleznev Date: Thu, 26 Mar 2026 13:27:15 +0100 Subject: [PATCH 09/10] Add dev-notebook --- docs/dev/notebooks/na_messages.ipynb | 2178 ++++++++++++++++++++++++++ 1 file changed, 2178 insertions(+) create mode 100644 docs/dev/notebooks/na_messages.ipynb diff --git a/docs/dev/notebooks/na_messages.ipynb b/docs/dev/notebooks/na_messages.ipynb new file mode 100644 index 00000000000..f9bc8e5cdc1 --- /dev/null +++ b/docs/dev/notebooks/na_messages.ipynb @@ -0,0 +1,2178 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "524046f7-1406-4283-b8f7-085374a13479", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " " + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import numpy as np\n", + "import pandas as pd\n", + "from lets_plot import *\n", + "\n", + "LetsPlot.setup_html()" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "c9e777c5-b196-4af8-83e8-bd1dbe00d9f7", + "metadata": {}, + "outputs": [], + "source": [ + "df = pd.DataFrame({\n", + " \"id\": list(range(1, 11)),\n", + " \"x\": [4, np.nan, 1, 9, 6, 2, 10, np.nan, 7, 5],\n", + " \"y\": [7, 1, 9, 10, 4, np.nan, 3, np.nan, 6, 5],\n", + " \"start\": [0,0,0,0,0,0,0,0,0,0]\n", + "})" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "1def5066-d800-47e6-b643-a7050254f781", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + "
\n", + "
\n", + " \n", + " \n", + " \n", + " " + ], + "text/plain": [ + "" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ggplot(df, aes(\"x\", \"y\")) + geom_point(na_rm=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "3172d54c-8ea8-4849-b8c9-cfe8a412923e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + "
\n", + "
\n", + " \n", + " \n", + " \n", + " " + ], + "text/plain": [ + "" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ggplot(df, aes(\"x\", \"y\")) + geom_pointdensity(na_rm=False, stat=\"identity\")" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "634c648e-9842-43d4-b51f-ad138b3b66ee", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + "
\n", + "
\n", + " \n", + " \n", + " \n", + " " + ], + "text/plain": [ + "" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ggplot(df, aes(\"x\", \"y\")) + geom_qq(na_rm=False, stat=\"identity\")" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "363f307c-513d-4387-9eaa-5ad36a470aa5", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + "
\n", + "
\n", + " \n", + " \n", + " \n", + " " + ], + "text/plain": [ + "" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ggplot(df, aes(\"x\", \"y\")) + geom_qq2(na_rm=False, stat=\"identity\")" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "a6ad4016-cead-41d3-a021-f68b4bc064de", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + "
\n", + "
\n", + " \n", + " \n", + " \n", + " " + ], + "text/plain": [ + "" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ggplot(df, aes(\"x\", \"y\")) + geom_area(na_rm=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "9a4d97f5-a7a6-4142-bfd4-afc6ca1b8664", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + "
\n", + "
\n", + " \n", + " \n", + " \n", + " " + ], + "text/plain": [ + "" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ggplot(df, aes(\"x\", \"y\")) + geom_jitter(na_rm=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "663bd9a3-464a-448d-8418-8393e4fbcc7d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + "
\n", + "
\n", + " \n", + " \n", + " \n", + " " + ], + "text/plain": [ + "" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ggplot(df, aes(\"x\", \"y\")) + geom_line(na_rm=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "68fc1a7c-1af6-4e76-b0b5-94b90149e87d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + "
\n", + "
\n", + " \n", + " \n", + " \n", + " " + ], + "text/plain": [ + "" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ggplot(df, aes(\"x\", \"y\")) + geom_map(na_rm=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "71073e5f-9970-44e7-83dc-83f4c688650f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + "
\n", + "
\n", + " \n", + " \n", + " \n", + " " + ], + "text/plain": [ + "" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ggplot(df, aes(\"x\", \"y\")) + geom_path(na_rm=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "433691b2-3cdb-449e-91c9-c90abafde839", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + "
\n", + "
\n", + " \n", + " \n", + " \n", + " " + ], + "text/plain": [ + "" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ggplot(df, aes(\"x\", \"y\")) + geom_polygon(na_rm=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "83cfd59a-3c3c-44b8-91a7-f7ca0116c19b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + "
\n", + "
\n", + " \n", + " \n", + " \n", + " " + ], + "text/plain": [ + "" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ggplot(df, aes(\"x\")) + geom_ribbon(aes(ymin=\"start\", ymax=\"y\"), na_rm=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "9eae189e-93f7-485f-99dc-e6802018b78f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "
\n", + "
\n", + "
\n", + " \n", + "
\n", + " \n", + "
\n", + "
\n", + " \n", + " \n", + " \n", + " " + ], + "text/plain": [ + "" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ggplot(df, aes(\"x\", \"y\")) + geom_step(na_rm=False)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.20" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From f7d4a004cf921a8bbd9e770412292daeb68fbd8d Mon Sep 17 00:00:00 2001 From: Ivan Seleznev Date: Tue, 31 Mar 2026 16:03:36 +0200 Subject: [PATCH 10/10] Improve dropped points reporting message --- .../org/jetbrains/letsPlot/core/plot/base/geom/GeomBase.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/GeomBase.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/GeomBase.kt index e61dcec9b2f..d887cdba268 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/GeomBase.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/geom/GeomBase.kt @@ -47,7 +47,7 @@ abstract class GeomBase : Geom { fun reportDroppedPoints(count: Int, ctx: GeomContext) { if (!ctx.removeNaMessages() && count > 0) { - ctx.consumeMessages(listOf("${ctx.geomKind().name.lowercase()}: removed $count data point(s)")) + ctx.consumeMessages(listOf("${ctx.geomKind().name.lowercase()}: Removed $count data point(s) containing missing values or values outside the scale range.")) } } @@ -60,8 +60,6 @@ abstract class GeomBase : Geom { ) companion object { - private const val SHOW_NA_MESSAGES = true - fun wrap(slimGroup: SvgSlimGroup): SvgGElement { val g = SvgGElement() g.isPrebuiltSubtree = true