Skip to content

Commit

Permalink
Make middle strip in geomCrossbar() optional [LPK-233]
Browse files Browse the repository at this point in the history
  • Loading branch information
alshan committed Feb 15, 2024
1 parent 44f80f3 commit 8a554dc
Show file tree
Hide file tree
Showing 7 changed files with 841 additions and 28 deletions.
778 changes: 778 additions & 0 deletions docs/dev/notebooks/crossBar_middle_LPK_233.ipynb

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions future_changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@
- Facets: "free scales" options are ignored by discrete axis [[#955](https://github.com/JetBrains/lets-plot/issues/955)].
- Bar width is too large when x-domain is defined via x-scale limits [[#1013](https://github.com/JetBrains/lets-plot/issues/1013)].
- How to hide only main tooltip? [[LPK-#232](https://github.com/JetBrains/lets-plot-kotlin/issues/232)].
- Make middle strip in `geomCrossbar()` optional [[LPK-#233](https://github.com/JetBrains/lets-plot-kotlin/issues/233)].
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ package org.jetbrains.letsPlot.core.commons.typedKey
* Maps typed keys to not-null values.
* The type parametr can't be a nullable type.
*/
class TypedKeyHashMap {
class TypedKeyHashMap private constructor(
private val map: MutableMap<TypedKey<*>, Any?>
) {

val map = hashMapOf<TypedKey<*>, Any?>()
constructor() : this(hashMapOf<TypedKey<*>, Any?>())

/**
* Throws NoSuchElementException if key is not present.
Expand Down Expand Up @@ -57,4 +59,8 @@ class TypedKeyHashMap {
@Suppress("UNCHECKED_CAST")
return map.keys as Set<TypedKey<T>>
}

fun makeCopy(): TypedKeyHashMap {
return TypedKeyHashMap(HashMap(this.map))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

package org.jetbrains.letsPlot.core.plot.base

import org.jetbrains.letsPlot.core.plot.base.aes.AestheticsDefaults
import org.jetbrains.letsPlot.core.plot.base.render.LegendKeyElementFactory
import org.jetbrains.letsPlot.core.plot.base.render.SvgRoot

Expand All @@ -13,4 +14,5 @@ interface Geom {
val wontRender: List<Aes<*>> get() = emptyList()
fun rangeIncludesZero(aes: Aes<*>): Boolean = false
fun build(root: SvgRoot, aesthetics: Aesthetics, pos: PositionAdjustment, coord: CoordinateSystem, ctx: GeomContext)
fun updateAestheticsDefaults(aestheticDefaults: AestheticsDefaults): AestheticsDefaults = aestheticDefaults
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,55 +5,67 @@

package org.jetbrains.letsPlot.core.plot.base.aes

import org.jetbrains.letsPlot.core.commons.typedKey.TypedKeyHashMap
import org.jetbrains.letsPlot.commons.values.Color
import org.jetbrains.letsPlot.core.commons.typedKey.TypedKeyHashMap
import org.jetbrains.letsPlot.core.plot.base.Aes
import org.jetbrains.letsPlot.core.plot.base.GeomKind
import org.jetbrains.letsPlot.core.plot.base.aes.AesInitValue.DEFAULT_ALPHA
import org.jetbrains.letsPlot.core.plot.base.render.point.NamedShape

open class AestheticsDefaults(geomTheme: GeomTheme) {

private val myDefaults = TypedKeyHashMap().apply {
for (aes in Aes.values()) {
// Safe cast because AesInitValue.get(aes) is guaranteed to return correct type.
@Suppress("UNCHECKED_CAST")
put(aes as Aes<Any>, AesInitValue[aes])
class AestheticsDefaults private constructor(
private val defaults: TypedKeyHashMap,
private val defaultsInLegend: TypedKeyHashMap,
) {

constructor(geomTheme: GeomTheme) : this(
defaults = TypedKeyHashMap().apply {
for (aes in Aes.values()) {
// Safe cast because AesInitValue.get(aes) is guaranteed to return correct type.
@Suppress("UNCHECKED_CAST")
put(aes as Aes<Any>, AesInitValue[aes])
}
// defaults from geom theme:
put(Aes.COLOR, geomTheme.color())
put(Aes.FILL, geomTheme.fill())
put(Aes.ALPHA, geomTheme.alpha())
put(Aes.SIZE, geomTheme.size())
put(Aes.LINEWIDTH, geomTheme.lineWidth())
put(Aes.STROKE, geomTheme.lineWidth())
},
defaultsInLegend = TypedKeyHashMap().apply {
put(Aes.ALPHA, DEFAULT_ALPHA)
}
// defaults from geom theme:
put(Aes.COLOR, geomTheme.color())
put(Aes.FILL, geomTheme.fill())
put(Aes.ALPHA, geomTheme.alpha())
put(Aes.SIZE, geomTheme.size())
put(Aes.LINEWIDTH, geomTheme.lineWidth())
put(Aes.STROKE, geomTheme.lineWidth())
}
private val myDefaultsInLegend = TypedKeyHashMap().apply {
put(Aes.ALPHA, DEFAULT_ALPHA)
}
)

private fun <T> update(aes: Aes<T>, defaultValue: T): AestheticsDefaults {
myDefaults.put(aes, defaultValue)
defaults.put(aes, defaultValue)
return this
}

private fun <T> updateInLegend(aes: Aes<T>, defaultValue: T): AestheticsDefaults {
myDefaultsInLegend.put(aes, defaultValue)
defaultsInLegend.put(aes, defaultValue)
return this
}

fun <T> defaultValue(aes: Aes<T>): T {
return myDefaults[aes]
return defaults[aes]
}

fun <T> defaultValueInLegend(aes: Aes<T>): T {
return if (myDefaultsInLegend.containsKey(aes)) {
myDefaultsInLegend[aes]
return if (defaultsInLegend.containsKey(aes)) {
defaultsInLegend[aes]
} else {
defaultValue(aes)
}
}

fun <T> with(aes: Aes<T>, defaultValue: T): AestheticsDefaults {
return AestheticsDefaults(
defaults = this.defaults.makeCopy().also { it.put(aes, defaultValue) },
defaultsInLegend = this.defaultsInLegend
)
}

companion object {
private fun point(geomTheme: GeomTheme): AestheticsDefaults {
return base(geomTheme)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,16 @@ package org.jetbrains.letsPlot.core.plot.base.geom
import org.jetbrains.letsPlot.commons.geometry.DoubleRectangle
import org.jetbrains.letsPlot.commons.geometry.DoubleVector
import org.jetbrains.letsPlot.core.plot.base.*
import org.jetbrains.letsPlot.core.plot.base.aes.AestheticsDefaults
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.datamodel.svg.dom.SvgShape

class CrossBarGeom(private val isVertical: Boolean) : GeomBase() {
class CrossBarGeom(
private val isVertical: Boolean
) : GeomBase() {

private val flipHelper = FlippableGeomHelper(isVertical)
var fattenMidline: Double = 2.5

Expand All @@ -25,6 +29,14 @@ class CrossBarGeom(private val isVertical: Boolean) : GeomBase() {
return listOf(Aes.XMIN, Aes.XMAX).map(::afterRotation)
}

override fun updateAestheticsDefaults(aestheticDefaults: AestheticsDefaults): AestheticsDefaults {
return if (isVertical) {
aestheticDefaults.with(Aes.Y, Double.NaN) // The middle bar is optional
} else {
aestheticDefaults.with(Aes.X, Double.NaN)
}
}

private fun afterRotation(aes: Aes<Double>): Aes<Double> {
return flipHelper.getEffectiveAes(aes)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,9 @@ class GeomLayerBuilder(
}
)
override val geomKind: GeomKind = geomProvider.geomKind
override val aestheticsDefaults: AestheticsDefaults = AestheticsDefaults.create(geomKind, geomTheme)
override val aestheticsDefaults: AestheticsDefaults = geom.updateAestheticsDefaults(
AestheticsDefaults.create(geomKind, geomTheme)
)

private val myRenderedAes: List<Aes<*>> = GeomMeta.renders(
geomProvider.geomKind,
Expand Down

0 comments on commit 8a554dc

Please sign in to comment.