From a5ecb0396c5b08f72ea2aeef04852851b9613852 Mon Sep 17 00:00:00 2001 From: Ivan Kupriyanov Date: Thu, 28 Nov 2024 17:48:31 +0100 Subject: [PATCH] Fix #1186 - Display integer values without fractional part in tooltips --- future_changes.md | 3 +- .../plot/base/tooltip/MappedDataAccess.kt | 2 + .../plot/builder/assemble/GeomLayerBuilder.kt | 4 +- .../builder/assemble/geom/PointDataAccess.kt | 5 +-- .../plot/builder/tooltip/TooltipFormatting.kt | 11 +++--- .../builder/tooltip/data/DataFrameField.kt | 6 ++- .../spec/config/TooltipCheckLabelInLines.kt | 38 +++++++++++++++++++ 7 files changed, 56 insertions(+), 13 deletions(-) diff --git a/future_changes.md b/future_changes.md index 953c4a4327e..4642a6bea11 100644 --- a/future_changes.md +++ b/future_changes.md @@ -13,4 +13,5 @@ - Wrong formatting when type='s' with explicit precision [[#1240](https://github.com/JetBrains/lets-plot/issues/1240)]. - Extra trim in formatted number when type='g' [[#1241](https://github.com/JetBrains/lets-plot/issues/1241)]. - Axis breaks are badly formatted if explicitly set [[#1245](https://github.com/JetBrains/lets-plot/issues/1245)]. -- Badly formatted zero break for the "~g" format [[#1246](https://github.com/JetBrains/lets-plot/issues/1246)]. \ No newline at end of file +- Badly formatted zero break for the "~g" format [[#1246](https://github.com/JetBrains/lets-plot/issues/1246)]. +- Display integer values without fractional part in tooltips [[#1186](https://github.com/JetBrains/lets-plot/issues/1186)]. \ No newline at end of file diff --git a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/tooltip/MappedDataAccess.kt b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/tooltip/MappedDataAccess.kt index 3587086ab06..4532a18f941 100644 --- a/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/tooltip/MappedDataAccess.kt +++ b/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/base/tooltip/MappedDataAccess.kt @@ -17,4 +17,6 @@ interface MappedDataAccess { // fun getMappedDataValue(aes: Aes<*>, index: Int, ctx: PlotContext): String fun getMappedDataLabel(aes: Aes<*>): String + + val defaultFormatters: Map String> } \ No newline at end of file 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 441b4718b96..010691e2bc9 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 @@ -365,13 +365,13 @@ class GeomLayerBuilder( } override fun createContextualMapping(): ContextualMapping { - val dataAccess = PointDataAccess(dataFrame, varBindings, scaleMap, isYOrientation) + val dataAccess = PointDataAccess(dataFrame, varBindings, scaleMap, isYOrientation, defaultFormatters) return contextualMappingProvider.createContextualMapping(dataAccess, dataFrame) } override fun createAnnotation(): Annotation? { return annotationProvider?.let { provider -> - val dataAccess = PointDataAccess(dataFrame, varBindings, scaleMap, isYOrientation) + val dataAccess = PointDataAccess(dataFrame, varBindings, scaleMap, isYOrientation, defaultFormatters) provider(dataAccess, dataFrame) } } diff --git a/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/assemble/geom/PointDataAccess.kt b/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/assemble/geom/PointDataAccess.kt index 2a6e66361e0..03319368faa 100644 --- a/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/assemble/geom/PointDataAccess.kt +++ b/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/assemble/geom/PointDataAccess.kt @@ -15,11 +15,10 @@ internal class PointDataAccess( private val data: DataFrame, private val bindings: Map, VarBinding>, private val scaleMap: Map, Scale>, - override val isYOrientation: Boolean + override val isYOrientation: Boolean, + override val defaultFormatters: Map String> ) : MappedDataAccess { - private val myFormatters = HashMap, (Any?) -> String>() - override fun isMapped(aes: Aes<*>) = bindings.containsKey(aes) override fun getOriginalValue(aes: Aes<*>, index: Int): Any? { diff --git a/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/tooltip/TooltipFormatting.kt b/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/tooltip/TooltipFormatting.kt index e221890aefb..38cc88ebdac 100644 --- a/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/tooltip/TooltipFormatting.kt +++ b/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/tooltip/TooltipFormatting.kt @@ -6,6 +6,7 @@ package org.jetbrains.letsPlot.core.plot.builder.tooltip import org.jetbrains.letsPlot.commons.formatting.string.StringFormat +import org.jetbrains.letsPlot.core.commons.data.DataType import org.jetbrains.letsPlot.core.plot.base.Aes import org.jetbrains.letsPlot.core.plot.base.DataFrame import org.jetbrains.letsPlot.core.plot.base.PlotContext @@ -30,13 +31,11 @@ internal object TooltipFormatting { } } - fun createFormatter(variable: DataFrame.Variable, expFormat: StringFormat.ExponentFormat): (Any) -> String { + fun createFormatter(variable: DataFrame.Variable, formatters: Map String>, expFormat: StringFormat.ExponentFormat): (Any) -> String { return when (variable) { - Stats.PROP, - Stats.SUMPROP -> StringFormat.forOneArg(".2f", formatFor = variable.name, expFormat = expFormat)::format - Stats.PROPPCT, - Stats.SUMPCT -> StringFormat.forOneArg("{.1f} %", formatFor = variable.name, expFormat = expFormat)::format - else -> { value -> value.toString() } + Stats.PROP, Stats.SUMPROP -> StringFormat.forOneArg(".2f", formatFor = variable.name, expFormat = expFormat)::format + Stats.PROPPCT, Stats.SUMPCT -> StringFormat.forOneArg("{.1f} %", formatFor = variable.name, expFormat = expFormat)::format + else -> formatters[variable.name] ?: DataType.UNKNOWN.formatter } } } \ No newline at end of file diff --git a/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/tooltip/data/DataFrameField.kt b/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/tooltip/data/DataFrameField.kt index a5f46f824f9..dcff3210b62 100644 --- a/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/tooltip/data/DataFrameField.kt +++ b/plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/tooltip/data/DataFrameField.kt @@ -18,6 +18,7 @@ class DataFrameField( private val format: String? = null ) : ValueSource { + private lateinit var myDataAccess: MappedDataAccess private lateinit var myDataFrame: DataFrame private lateinit var myVariable: DataFrame.Variable private var myFormatter: ((Any) -> String)? = null @@ -26,7 +27,7 @@ class DataFrameField( require(myFormatter == null) myFormatter = when (format) { - null -> TooltipFormatting.createFormatter(myVariable, expFormat) + null -> TooltipFormatting.createFormatter(myVariable, myDataAccess.defaultFormatters, expFormat) else -> StringFormat.forOneArg(format, formatFor = name, expFormat = expFormat)::format } return myFormatter!! @@ -37,6 +38,9 @@ class DataFrameField( override val isAxis: Boolean = false override fun initDataContext(data: DataFrame, mappedDataAccess: MappedDataAccess) { + require(!::myDataAccess.isInitialized) { "Data context can be initialized only once" } + myDataAccess = mappedDataAccess + require(!::myDataFrame.isInitialized) { "Data context can be initialized only once" } myDataFrame = data diff --git a/plot-stem/src/jvmTest/kotlin/org/jetbrains/letsPlot/core/spec/config/TooltipCheckLabelInLines.kt b/plot-stem/src/jvmTest/kotlin/org/jetbrains/letsPlot/core/spec/config/TooltipCheckLabelInLines.kt index c8d9cd89e08..358bd6f5cc9 100644 --- a/plot-stem/src/jvmTest/kotlin/org/jetbrains/letsPlot/core/spec/config/TooltipCheckLabelInLines.kt +++ b/plot-stem/src/jvmTest/kotlin/org/jetbrains/letsPlot/core/spec/config/TooltipCheckLabelInLines.kt @@ -235,4 +235,42 @@ class TooltipCheckLabelInLines { expectedLines = listOf("b: 10") ) } + + @Test + fun `issue 1186 - dataframe variable should be formatted using the DataType from series_annotations`() { + val spec = """ + |{ + | "data": { + | "l": [ 3.0 ], + | "b": [ 4.0 ] + | }, + | "mapping": { "color": "l" }, + | "data_meta": { + | "series_annotations": [ + | { "type": "int", "column": "l" }, + | { "type": "int", "column": "b" } + | ] + | }, + | "ggsize": { "width": 300.0, "height": 200.0 }, + | "kind": "plot", + | "layers": [ + | { + | "geom": "point", + | "tooltips": { + | "lines": [ + | "l is @l", + | "b is @b" + | ] + | } + | } + | ] + |} + """.trimMargin() + + val layer = TestingGeomLayersBuilder.getSingleGeomLayer(spec) + assertGeneralTooltip( + layer, + expectedLines = listOf("l is 3", "b is 4") + ) + } }