Skip to content

Commit

Permalink
When creating scale for empty data, check configuration to choose a t…
Browse files Browse the repository at this point in the history
…ype of the scale (discrete/continuous). Related to #271.
  • Loading branch information
alshan committed Aug 3, 2021
1 parent fac4f8b commit 90ebf5d
Show file tree
Hide file tree
Showing 11 changed files with 82 additions and 38 deletions.
@@ -0,0 +1,15 @@
/*
* Copyright (c) 2019. JetBrains s.r.o.
* Use of this source code is governed by the MIT license that can be found in the LICENSE file.
*/

package jetbrains.datalore.plot.builder.scale

import jetbrains.datalore.plot.builder.scale.provider.MapperProviderBase

abstract class ContinuousOnlyMapperProvider<T>(naValue: T) : MapperProviderBase<T>(naValue) {
override fun createDiscreteMapper(domainValues: Collection<*>): GuideMapper<T> {
val domainRepr = domainValues.joinToString(limit = 3) { "'$it'" }
throw IllegalStateException("[${this::class.simpleName}] Can't create mapper for discrete domain: $domainRepr")
}
}
Expand Up @@ -7,19 +7,15 @@ package jetbrains.datalore.plot.builder.scale

import jetbrains.datalore.base.gcommon.collect.ClosedRange
import jetbrains.datalore.plot.base.ContinuousTransform
import jetbrains.datalore.plot.builder.scale.provider.MapperProviderBase

open class MapperProviderAdapter<T> : MapperProvider<T> {
override fun createDiscreteMapper(domainValues: Collection<*>): GuideMapper<T> {
throw IllegalStateException("Can't create mapper for discrete domain: ${domainValues.map { "'$it'" }
.joinToString(limit = 3)}")
}

abstract class DiscreteOnlyMapperProvider<T>(naValue: T) : MapperProviderBase<T>(naValue) {
override fun createContinuousMapper(
domain: ClosedRange<Double>,
lowerLimit: Double?,
upperLimit: Double?,
trans: ContinuousTransform
): GuideMapper<T> {
throw IllegalStateException("Can't create mapper for continuous domain $domain")
throw IllegalStateException("[${this::class.simpleName}] Can't create mapper for continuous domain $domain")
}
}
Expand Up @@ -12,8 +12,9 @@ import jetbrains.datalore.plot.base.Scale
interface ScaleProvider<T> {
val discreteDomain: Boolean
val breaks: List<Any>?
val discreteDomainLimits: List<Any>?
val limits: List<Any?>? // when 'continuous' limits, NULL means undefined upper or lower limit.
val continuousTransform: ContinuousTransform
val mapperProvider: MapperProvider<T>

/**
* Create scale for discrete input (domain)
Expand Down
Expand Up @@ -139,28 +139,22 @@ class ScaleProviderBuilder<T>(private val aes: Aes<T>) {
return MyScaleProvider(this)
}

private class MyScaleProvider<T>(b: ScaleProviderBuilder<T>) :
ScaleProvider<T> {
private class MyScaleProvider<T>(b: ScaleProviderBuilder<T>) : ScaleProvider<T> {

private val myName: String? = b.myName

private val myLabels: List<String>? = b.myLabels?.let { ArrayList(it) }
private val myLabelFormat: String? = b.myLabelFormat
private val myMultiplicativeExpand: Double? = b.myMultiplicativeExpand
private val myAdditiveExpand: Double? = b.myAdditiveExpand
private val myLimits: List<Any?>? = b.myLimits?.let { ArrayList(it) }
private val discreteDomainReverse: Boolean = b.myDiscreteDomainReverse

private val myBreaksGenerator: BreaksGenerator? = b.myBreaksGenerator

private val myAes: Aes<T> = b.aes
private val mapperProvider: MapperProvider<T> = b.mapperProvider

override val breaks: List<Any>? = b.myBreaks?.let { ArrayList(it) }
override val discreteDomain: Boolean = b.myDiscreteDomain

// This is only to use by 'discrete' transforms/scales
override val discreteDomainLimits: List<Any>? = myLimits?.filterNotNull()
override val mapperProvider: MapperProvider<T> = b.mapperProvider
override val breaks: List<Any>? = b.myBreaks?.let { ArrayList(it) }
override val limits: List<Any?>? = b.myLimits?.let { ArrayList(it) }

override val continuousTransform: ContinuousTransform = b.myContinuousTransform

Expand Down Expand Up @@ -191,7 +185,7 @@ class ScaleProviderBuilder<T>(private val aes: Aes<T>) {
mapper
)

val discreteLimits = discreteDomainLimits?.let {
val discreteLimits = limits?.filterNotNull()?.let {
if (discreteDomainReverse) {
it.reversed()
} else {
Expand All @@ -216,9 +210,9 @@ class ScaleProviderBuilder<T>(private val aes: Aes<T>) {

var lowerLimit: Double? = null
var upperLimit: Double? = null
if (myLimits != null) {
if (limits != null) {
var lower = true
for (limit in myLimits) {
for (limit in limits) {
if (limit is Number) {
val v = limit.toDouble()
if (v.isFinite()) {
Expand Down Expand Up @@ -263,7 +257,7 @@ class ScaleProviderBuilder<T>(private val aes: Aes<T>) {
.build()
}

if (myLimits != null) {
if (limits != null) {
val with = scale.with()
if (lowerLimit != null) {
with.lowerLimit(lowerLimit)
Expand Down
Expand Up @@ -10,9 +10,9 @@ import jetbrains.datalore.plot.base.Aes.Companion.ALPHA
import jetbrains.datalore.plot.builder.scale.DefaultNaValue

class AlphaMapperProvider(
range: ClosedRange<Double>,
naValue: Double) :
LinearNormalizingMapperProvider(range, naValue) {
range: ClosedRange<Double>,
naValue: Double
) : LinearNormalizingMapperProvider(range, naValue) {

companion object {
private val DEF_RANGE = ClosedRange(0.1, 1.0)
Expand Down
Expand Up @@ -9,15 +9,17 @@ import jetbrains.datalore.base.gcommon.collect.ClosedRange
import jetbrains.datalore.base.values.Color
import jetbrains.datalore.plot.base.ContinuousTransform
import jetbrains.datalore.plot.base.scale.MapperUtil
import jetbrains.datalore.plot.builder.scale.ContinuousOnlyMapperProvider
import jetbrains.datalore.plot.builder.scale.GuideMapper
import jetbrains.datalore.plot.builder.scale.mapper.ColorMapper
import jetbrains.datalore.plot.builder.scale.mapper.GuideMappers
import jetbrains.datalore.plot.common.data.SeriesUtil
import kotlin.math.max
import kotlin.math.min

class ColorGradient2MapperProvider(low: Color?, mid: Color?, high: Color?, midpoint: Double?, naValue: Color) :
MapperProviderBase<Color>(naValue) {
class ColorGradient2MapperProvider(
low: Color?, mid: Color?, high: Color?, midpoint: Double?, naValue: Color
) : ContinuousOnlyMapperProvider<Color>(naValue) {

private val myLow: Color
private val myMid: Color
Expand Down
Expand Up @@ -8,6 +8,7 @@ package jetbrains.datalore.plot.builder.scale.provider
import jetbrains.datalore.base.gcommon.collect.ClosedRange
import jetbrains.datalore.plot.base.ContinuousTransform
import jetbrains.datalore.plot.base.scale.MapperUtil
import jetbrains.datalore.plot.builder.scale.ContinuousOnlyMapperProvider
import jetbrains.datalore.plot.builder.scale.GuideMapper
import jetbrains.datalore.plot.builder.scale.mapper.GuideMappers

Expand All @@ -17,7 +18,7 @@ import jetbrains.datalore.plot.builder.scale.mapper.GuideMappers
open class DirectlyProportionalMapperProvider(
private val max: Double,
naValue: Double
) : MapperProviderBase<Double>(naValue) {
) : ContinuousOnlyMapperProvider<Double>(naValue) {

override fun createContinuousMapper(
domain: ClosedRange<Double>,
Expand Down
Expand Up @@ -5,12 +5,14 @@

package jetbrains.datalore.plot.builder.scale.provider

import jetbrains.datalore.plot.builder.scale.DiscreteOnlyMapperProvider
import jetbrains.datalore.plot.builder.scale.GuideMapper
import jetbrains.datalore.plot.builder.scale.mapper.GuideMappers

open class IdentityDiscreteMapperProvider<T>(
private val inputConverter: (Any?) -> T?, naValue: T
) : MapperProviderBase<T>(naValue) {
private val inputConverter: (Any?) -> T?,
naValue: T
) : DiscreteOnlyMapperProvider<T>(naValue) {

override fun createDiscreteMapper(domainValues: Collection<*>): GuideMapper<T> {
val outputValues = ArrayList<T>()
Expand Down
Expand Up @@ -5,6 +5,6 @@

package jetbrains.datalore.plot.builder.scale.provider

import jetbrains.datalore.plot.builder.scale.MapperProviderAdapter
import jetbrains.datalore.plot.builder.scale.MapperProvider

abstract class MapperProviderBase<T>(protected val naValue: T) : MapperProviderAdapter<T>()
abstract class MapperProviderBase<T>(protected val naValue: T) : MapperProvider<T>
Expand Up @@ -12,6 +12,8 @@ import jetbrains.datalore.plot.base.scale.transform.Transforms
import jetbrains.datalore.plot.builder.VarBinding
import jetbrains.datalore.plot.builder.assemble.PlotFacets
import jetbrains.datalore.plot.builder.assemble.TypedScaleMap
import jetbrains.datalore.plot.builder.scale.ContinuousOnlyMapperProvider
import jetbrains.datalore.plot.builder.scale.DiscreteOnlyMapperProvider
import jetbrains.datalore.plot.builder.scale.ScaleProvider
import jetbrains.datalore.plot.builder.scale.ScaleProviderHelper
import jetbrains.datalore.plot.common.data.SeriesUtil
Expand Down Expand Up @@ -168,13 +170,18 @@ object PlotConfigUtil {
// Extract "discrete" aes set.
val discreteAesSet: MutableSet<Aes<*>> = HashSet()
for (aes in aesSet) {
if (scaleProviderByAes.getValue(aes).discreteDomain) {
val scaleProvider = scaleProviderByAes.getValue(aes)
if (scaleProvider.discreteDomain) {
discreteAesSet.add(aes)
} else if (variablesByMappedAes.containsKey(aes)) {
val variables = variablesByMappedAes.getValue(aes)
val anyNotNumericData = variables.any {
val data = dataByVarBinding.getValue(VarBinding(it, aes))
!data.isNumeric(it)
if (data.isEmpty(it)) {
isDiscreteScaleForEmptyData(scaleProvider)
} else {
!data.isNumeric(it)
}
}
if (anyNotNumericData) {
discreteAesSet.add(aes)
Expand Down Expand Up @@ -215,7 +222,7 @@ object PlotConfigUtil {
val effectiveDomain = (scaleBreaks + domainValues).distinct()
val transform = DiscreteTransform(
domainValues = effectiveDomain,
domainLimits = scaleProvider.discreteDomainLimits ?: emptyList()
domainLimits = (scaleProvider.limits ?: emptyList()).filterNotNull()
)
discreteTransformByAes[aes] = transform
}
Expand Down Expand Up @@ -392,4 +399,31 @@ object PlotConfigUtil {
dataRange
}
}

private fun isDiscreteScaleForEmptyData(scaleProvider: ScaleProvider<*>): Boolean {
// Empty data is neither 'discrete' nor 'numeric'.
// Which scale to build?
if (scaleProvider.discreteDomain) return true

val mapperProvider = scaleProvider.mapperProvider
if (mapperProvider is DiscreteOnlyMapperProvider) return true
if (mapperProvider is ContinuousOnlyMapperProvider) return false

val breaks = scaleProvider.breaks
val limits = scaleProvider.limits

val breaksAreDiscrete = breaks?.let {
it.any { !(it is Number) }
} ?: false

val limitsAreDiscrete = limits?.let {
// Not a list of 2 numbers.
when {
it.size > 2 -> true
else -> it.filterNotNull().any { !(it is Number) }
}
} ?: false

return breaksAreDiscrete || limitsAreDiscrete
}
}
Expand Up @@ -5,7 +5,6 @@

package jetbrains.datalore.plot.config

import jetbrains.datalore.base.gcommon.base.Preconditions.checkArgument
import jetbrains.datalore.base.values.Color
import jetbrains.datalore.plot.base.Aes
import jetbrains.datalore.plot.base.scale.Mappers.nullable
Expand Down Expand Up @@ -239,8 +238,8 @@ class ScaleConfig<T>(options: Map<String, Any>) : OptionsAccessor(options) {

fun aesOrFail(options: Map<String, Any>): Aes<*> {
val accessor = OptionsAccessor(options)
checkArgument(accessor.has(AES), "Required parameter '$AES' is missing")
return Option.Mapping.toAes(accessor.getString(AES)!!)
require(accessor.has(AES)) { "Required parameter '$AES' is missing" }
return Option.Mapping.toAes(accessor.getStringSafe(AES))
}

fun <T> createIdentityMapperProvider(aes: Aes<T>, naValue: T): MapperProvider<T> {
Expand Down

0 comments on commit 90ebf5d

Please sign in to comment.