diff --git a/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/coord/CoordProviders.kt b/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/coord/CoordProviders.kt index a8c549d5af2..e3c169c9541 100644 --- a/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/coord/CoordProviders.kt +++ b/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/coord/CoordProviders.kt @@ -22,12 +22,14 @@ object CoordProviders { } fun map( + ratio: Double, xLim: ClosedRange? = null, yLim: ClosedRange? = null ): CoordProvider { // Mercator projection is cylindrical thus we don't really need 'projection X' return ProjectionCoordProvider.withProjectionY( MercatorProjectionY(), + ratio, xLim, yLim ) diff --git a/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/coord/ProjectionCoordProvider.kt b/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/coord/ProjectionCoordProvider.kt index afe8a13f6e9..731fd0352b3 100644 --- a/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/coord/ProjectionCoordProvider.kt +++ b/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/coord/ProjectionCoordProvider.kt @@ -6,6 +6,9 @@ package jetbrains.datalore.plot.builder.coord import jetbrains.datalore.base.gcommon.collect.ClosedRange +import jetbrains.datalore.base.geometry.DoubleVector +import jetbrains.datalore.base.spatial.MercatorUtils +import jetbrains.datalore.base.values.Pair import jetbrains.datalore.plot.base.Scale import jetbrains.datalore.plot.base.coord.Projection import jetbrains.datalore.plot.base.scale.Mappers @@ -15,10 +18,11 @@ import jetbrains.datalore.plot.common.data.SeriesUtil internal class ProjectionCoordProvider private constructor( private val myProjectionX: Projection?, private val myProjectionY: Projection?, + ratio: Double, xLim: ClosedRange?, yLim: ClosedRange? )// square grid - : FixedRatioCoordProvider(1.0, xLim, yLim) { + : FixedRatioCoordProvider(ratio, xLim, yLim) { override fun buildAxisScaleX( scaleProto: Scale, @@ -54,15 +58,18 @@ internal class ProjectionCoordProvider private constructor( } else super.buildAxisScaleY(scaleProto, domain, axisLength, breaks) } + companion object { fun withProjectionY( projectionY: Projection, + ratio: Double, xLim: ClosedRange?, yLim: ClosedRange? ): CoordProvider { return ProjectionCoordProvider( null, projectionY, + ratio, xLim, yLim ) diff --git a/plot-builder/src/jvmTest/kotlin/plot/builder/coord/CoordMapTest.kt b/plot-builder/src/jvmTest/kotlin/plot/builder/coord/CoordMapTest.kt index 20a9d193629..bee89fdc68f 100644 --- a/plot-builder/src/jvmTest/kotlin/plot/builder/coord/CoordMapTest.kt +++ b/plot-builder/src/jvmTest/kotlin/plot/builder/coord/CoordMapTest.kt @@ -56,7 +56,7 @@ internal class CoordMapTest : jetbrains.datalore.plot.builder.coord.CoordTestBas } companion object { - private val PROVIDER = CoordProviders.map() + private val PROVIDER = CoordProviders.map(1.0) private val DATA_SPAN = DoubleVector(10.0, 10.0) } diff --git a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/CoordConfig.kt b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/CoordConfig.kt index 3dce03ea507..175251a2c11 100644 --- a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/CoordConfig.kt +++ b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/CoordConfig.kt @@ -7,7 +7,8 @@ package jetbrains.datalore.plot.config import jetbrains.datalore.plot.builder.coord.CoordProvider -class CoordConfig private constructor(name: String, options: Map) : OptionsAccessor(options, emptyMap()) { +class CoordConfig private constructor(name: String, options: Map) : + OptionsAccessor(options, emptyMap()) { val coord: CoordProvider = CoordProto.createCoordProvider(name, this) diff --git a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/CoordProto.kt b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/CoordProto.kt index 8d39aa7afa3..5eac382fa18 100644 --- a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/CoordProto.kt +++ b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/CoordProto.kt @@ -14,9 +14,9 @@ import jetbrains.datalore.plot.config.Option.CoordName.MAP internal object CoordProto { // option names - private const val X_LIM = "xlim" - private const val Y_LIM = "ylim" - private const val RATIO = "ratio" + const val X_LIM = "xlim" + const val Y_LIM = "ylim" + const val RATIO = "ratio" private const val EXPAND = "expand" // todo private const val ORIENTATION = "orientation" // todo private const val PROJECTION = "projection" // todo @@ -27,7 +27,7 @@ internal object CoordProto { return when (coordName) { CARTESIAN -> CoordProviders.cartesian(xLim, yLim) FIXED -> CoordProviders.fixed(options.getDouble(RATIO) ?: 1.0, xLim, yLim) - MAP -> CoordProviders.map(xLim, yLim) + MAP -> CoordProviders.map(options.getDouble(RATIO) ?: 1.0, xLim, yLim) else -> throw IllegalArgumentException("Unknown coordinate system name: '$coordName'") } } diff --git a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProtoClientSide.kt b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProtoClientSide.kt index aaed03986fc..80f92f4d676 100644 --- a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProtoClientSide.kt +++ b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProtoClientSide.kt @@ -35,8 +35,6 @@ class GeomProtoClientSide(geomKind: GeomKind) : GeomProto(geomKind) { GeomKind.RASTER, GeomKind.IMAGE -> CoordProviders.fixed(1.0) - GeomKind.MAP -> CoordProviders.map() - else -> null } diff --git a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/PlotConfigClientSide.kt b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/PlotConfigClientSide.kt index bf86c0e2ad0..9990cb0dd52 100644 --- a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/PlotConfigClientSide.kt +++ b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/PlotConfigClientSide.kt @@ -7,6 +7,7 @@ package jetbrains.datalore.plot.config import jetbrains.datalore.plot.base.Aes import jetbrains.datalore.plot.base.DataFrame +import jetbrains.datalore.plot.base.GeomKind import jetbrains.datalore.plot.builder.assemble.GuideOptions import jetbrains.datalore.plot.builder.assemble.TypedScaleProviderMap import jetbrains.datalore.plot.builder.coord.CoordProvider @@ -17,6 +18,7 @@ import jetbrains.datalore.plot.config.PlotConfigClientSideUtil.createGuideOption import jetbrains.datalore.plot.config.theme.ThemeConfig import jetbrains.datalore.plot.config.transform.PlotSpecTransform import jetbrains.datalore.plot.config.transform.migration.MoveGeomPropertiesToLayerMigration +import jetbrains.datalore.plot.config.PlotConfigClientSideUtil.getMapCoordinateProvider class PlotConfigClientSide private constructor(opts: Map) : PlotConfig(opts) { @@ -28,20 +30,38 @@ class PlotConfigClientSide private constructor(opts: Map) : PlotCon get() = true init { + coordProvider = createCoordProvider() + guideOptionsMap = createGuideOptionsMap(this.scaleConfigs) + } + private fun createCoordProvider(): CoordProvider { val coord = CoordConfig.create(get(COORD)!!) var coordProvider = coord.coord if (!hasOwn(COORD)) { // if coord wasn't set explicitly then geom can provide its own preferred coord system for (layerConfig in layerConfigs) { val geomProtoClientSide = layerConfig.geomProto as GeomProtoClientSide - if (geomProtoClientSide.hasPreferredCoordinateSystem()) { + if (layerConfig.geomProto.geomKind == GeomKind.MAP) { + val data = layerConfig.combinedData + val varX = layerConfig.getVariableForAes(Aes.X) + val varY = layerConfig.getVariableForAes(Aes.Y) + + if (varX != null && varY != null) { + val xRange = data.range(varX) + val yRange = data.range(varY) + + val xLim = getRangeOrNull(CoordProto.X_LIM) + val yLim = getRangeOrNull(CoordProto.Y_LIM) + + coordProvider = getMapCoordinateProvider(xRange!!, yRange!!, xLim, yLim) + } + } else if (geomProtoClientSide.hasPreferredCoordinateSystem()) { coordProvider = geomProtoClientSide.preferredCoordinateSystem() } } } - this.coordProvider = coordProvider - guideOptionsMap = createGuideOptionsMap(this.scaleConfigs) + + return coordProvider } override fun createLayerConfig( diff --git a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/PlotConfigClientSideUtil.kt b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/PlotConfigClientSideUtil.kt index 24c9941fb15..42cc5f37922 100644 --- a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/PlotConfigClientSideUtil.kt +++ b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/PlotConfigClientSideUtil.kt @@ -6,6 +6,8 @@ package jetbrains.datalore.plot.config import jetbrains.datalore.base.gcommon.base.Preconditions.checkState +import jetbrains.datalore.base.gcommon.collect.ClosedRange +import jetbrains.datalore.base.spatial.MercatorUtils import jetbrains.datalore.plot.base.Aes import jetbrains.datalore.plot.base.DataFrame import jetbrains.datalore.plot.base.GeomKind @@ -15,7 +17,9 @@ import jetbrains.datalore.plot.builder.assemble.GeomLayerBuilder import jetbrains.datalore.plot.builder.assemble.GuideOptions import jetbrains.datalore.plot.builder.assemble.PlotAssembler import jetbrains.datalore.plot.builder.assemble.TypedScaleProviderMap +import jetbrains.datalore.plot.builder.coord.CoordProvider import jetbrains.datalore.plot.builder.interact.GeomInteraction +import jetbrains.datalore.plot.common.data.SeriesUtil object PlotConfigClientSideUtil { internal fun createGuideOptionsMap(scaleConfigs: List>): Map, GuideOptions> { @@ -68,7 +72,8 @@ object PlotConfigClientSideUtil { if (layerBuilders.size == layerIndex) { val layerConfig = cfg.layerConfigs[layerIndex] - val geomInteraction = GeomInteractionUtil.configGeomTargets(layerConfig, isMultilayer, isLiveMap, cfg.theme) + val geomInteraction = + GeomInteractionUtil.configGeomTargets(layerConfig, isMultilayer, isLiveMap, cfg.theme) layerBuilders.add(createLayerBuilder(layerConfig, scaleProvidersMap, geomInteraction)) } @@ -137,4 +142,49 @@ object PlotConfigClientSideUtil { return layerBuilder } + + private fun doProjection(proj: ((Double) -> Double), range: ClosedRange?) = range?.let { + ClosedRange(proj(range.lowerEnd), proj(range.upperEnd)) + } + + fun getMapCoordinateProvider( + xDomain: ClosedRange, + yDomain: ClosedRange, + xLim: ClosedRange?, + yLim: ClosedRange? + ): CoordProvider { + val projDX = SeriesUtil.span( + doProjection({ MercatorUtils.getMercatorX(it) }, xDomain)!! + ) + + val projDY = SeriesUtil.span( + doProjection({ MercatorUtils.getMercatorY(it) }, yDomain)!! + ) + + val dx = SeriesUtil.span(xDomain) + val dy = SeriesUtil.span(yDomain) + + val ratio = (projDY / projDX) / (dy / dx) + + @Suppress("NAME_SHADOWING") + val xLim = doProjection({ MercatorUtils.getMercatorX(it) }, xLim) + + @Suppress("NAME_SHADOWING") + val yLim = doProjection({ MercatorUtils.getMercatorY(it) }, yLim) + + val opts: MutableMap = mutableMapOf( + Option.Meta.NAME to Option.CoordName.MAP, + CoordProto.RATIO to ratio + ) + + if (xLim != null) { + opts[CoordProto.X_LIM] = xLim + } + + if (yLim != null) { + opts[CoordProto.Y_LIM] = yLim + } + + return CoordConfig.create(opts).coord + } } diff --git a/plot-demo/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/geom/PolygonWithCoordMapDemo.kt b/plot-demo/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/geom/PolygonWithCoordMapDemo.kt index 29ca9266567..fb018384cc3 100644 --- a/plot-demo/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/geom/PolygonWithCoordMapDemo.kt +++ b/plot-demo/src/commonMain/kotlin/jetbrains/datalore/plotDemo/model/geom/PolygonWithCoordMapDemo.kt @@ -19,6 +19,7 @@ import jetbrains.datalore.plot.base.scale.Mappers import jetbrains.datalore.plot.builder.coord.CoordProviders import jetbrains.datalore.plot.builder.scale.mapper.ColorMapper import jetbrains.datalore.plot.common.data.SeriesUtil +import jetbrains.datalore.plot.config.PlotConfigClientSideUtil.getMapCoordinateProvider import jetbrains.datalore.plotDemo.data.KansasPolygon.KANSAS_X import jetbrains.datalore.plotDemo.data.KansasPolygon.KANSAS_Y import jetbrains.datalore.plotDemo.model.SimpleDemoBase @@ -80,8 +81,11 @@ open class PolygonWithCoordMapDemo : SimpleDemoBase() { .color(constant(Color.DARK_MAGENTA)) .alpha(constant(0.5)) .build() - val coord = CoordProviders.map() + + val coord = getMapCoordinateProvider(domainX, domainY, null, null) .createCoordinateSystem(domainX, lengthX, domainY, lengthY) + + val layer = jetbrains.datalore.plot.builder.SvgLayerRenderer( aes, PolygonGeom(),