Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Legend override_aes #1115

Draft
wants to merge 12 commits into
base: master
Choose a base branch
from
Next Next commit
override_aes in guide_legend().
  • Loading branch information
OLarionova-HORIS committed May 14, 2024
commit d861a6329371145e7295190ab2e750be5d1edb9a
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class LegendAssembler(
fun addLayer(
keyFactory: LegendKeyElementFactory,
aesList: List<Aes<*>>,
overrideAesValues: Map<Aes<*>, Any>,
constantByAes: Map<Aes<*>, Any>,
aestheticsDefaults: AestheticsDefaults,
colorByAes: Aes<Color>,
Expand All @@ -50,6 +51,7 @@ class LegendAssembler(
LegendLayer(
keyFactory,
aesList,
overrideAesValues,
constantByAes,
aestheticsDefaults,
scaleMappers,
Expand Down Expand Up @@ -121,6 +123,7 @@ class LegendAssembler(
private class LegendLayer(
val keyElementFactory: LegendKeyElementFactory,
val aesList: List<Aes<*>>,
overrideAesValues: Map<Aes<*>, Any>,
constantByAes: Map<Aes<*>, Any>,
aestheticsDefaults: AestheticsDefaults,
scaleMappers: Map<Aes<*>, ScaleMapper<*>>,
Expand Down Expand Up @@ -150,6 +153,9 @@ class LegendAssembler(
val labels = scaleBreaks.labels
for ((label, aesValue) in labels.zip(aesValues)) {
aesValuesByLabel.getOrPut(label) { HashMap() }[aes] = aesValue
overrideAesValues.forEach { (aesToOverride, v) ->
aesValuesByLabel.getOrPut(label) { HashMap() }[aesToOverride] = v
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@

package org.jetbrains.letsPlot.core.plot.builder.assemble

import org.jetbrains.letsPlot.core.plot.base.Aes

class LegendOptions constructor(
val colCount: Int? = null,
val rowCount: Int? = null,
val byRow: Boolean = false,
val overrideAesValues: Map<Aes<*>, Any>? = null,
isReverse: Boolean = false
) : GuideOptions(isReverse) {
init {
Expand All @@ -26,7 +29,7 @@ class LegendOptions constructor(

override fun withReverse(reverse: Boolean): LegendOptions {
return LegendOptions(
colCount, rowCount, byRow, isReverse = reverse
colCount, rowCount, byRow, overrideAesValues, isReverse = reverse
)
}

Expand All @@ -39,6 +42,7 @@ class LegendOptions constructor(
if (colCount != other.colCount) return false
if (rowCount != other.rowCount) return false
if (byRow != other.byRow) return false
if (overrideAesValues != other.overrideAesValues) return false

return true
}
Expand All @@ -47,6 +51,7 @@ class LegendOptions constructor(
var result = colCount ?: 0
result = 31 * result + (rowCount ?: 0)
result = 31 * result + byRow.hashCode()
result = 31 * result + (overrideAesValues?.hashCode() ?: 0)
return result
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,20 @@ internal object PlotAssemblerUtil {
val aesListForScaleName = aesListByScaleName.getValue(scaleName)
val legendKeyFactory = layerInfo.legendKeyElementFactory
val aestheticsDefaults = layerInfo.aestheticsDefaults

val overrideAesValues = mutableMapOf<Aes<*>, Any>()
aesListForScaleName.forEach { aes ->
val overrideOptionsForAes = (guideOptionsMap[aes] as? LegendOptions)?.overrideAesValues
if (!overrideOptionsForAes.isNullOrEmpty()) {
// ToDo Need to check for conflicting settings?
overrideAesValues += overrideOptionsForAes
}
}

legendAssembler.addLayer(
legendKeyFactory,
aesListForScaleName,
overrideAesValues,
layerConstantByAes,
aestheticsDefaults,
layerInfo.colorByAes,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,7 @@ object Option {
const val ROW_COUNT = "nrow"
const val COL_COUNT = "ncol"
const val BY_ROW = "byrow"
const val OVERRIDE_AES = "override_aes"
}

object ColorBar {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@

package org.jetbrains.letsPlot.core.spec.config

import org.jetbrains.letsPlot.core.plot.base.Aes
import org.jetbrains.letsPlot.core.plot.builder.assemble.ColorBarOptions
import org.jetbrains.letsPlot.core.plot.builder.assemble.GuideOptions
import org.jetbrains.letsPlot.core.plot.builder.assemble.LegendOptions
import org.jetbrains.letsPlot.core.spec.Option
import org.jetbrains.letsPlot.core.spec.Option.Guide.COLOR_BAR
import org.jetbrains.letsPlot.core.spec.Option.Guide.COLOR_BAR_GB
import org.jetbrains.letsPlot.core.spec.Option.Guide.ColorBar.BIN_COUNT
Expand All @@ -16,41 +18,67 @@ import org.jetbrains.letsPlot.core.spec.Option.Guide.ColorBar.WIDTH
import org.jetbrains.letsPlot.core.spec.Option.Guide.LEGEND
import org.jetbrains.letsPlot.core.spec.Option.Guide.Legend.BY_ROW
import org.jetbrains.letsPlot.core.spec.Option.Guide.Legend.COL_COUNT
import org.jetbrains.letsPlot.core.spec.Option.Guide.Legend.OVERRIDE_AES
import org.jetbrains.letsPlot.core.spec.Option.Guide.Legend.ROW_COUNT
import org.jetbrains.letsPlot.core.spec.Option.Guide.NONE
import org.jetbrains.letsPlot.core.spec.Option.Guide.REVERSE
import org.jetbrains.letsPlot.core.spec.conversion.AesOptionConversion
import kotlin.math.max

abstract class GuideConfig private constructor(opts: Map<String, Any>) : OptionsAccessor(opts) {

fun createGuideOptions(): GuideOptions {
val options = createGuideOptionsIntern()
fun createGuideOptions(aopConversion: AesOptionConversion): GuideOptions {
val options = createGuideOptionsIntern(aopConversion)
return options.withReverse(getBoolean(REVERSE))
}

protected abstract fun createGuideOptionsIntern(): GuideOptions
protected abstract fun createGuideOptionsIntern(aopConversion: AesOptionConversion): GuideOptions

private class GuideNoneConfig internal constructor() : GuideConfig(emptyMap()) {

override fun createGuideOptionsIntern(): GuideOptions {
override fun createGuideOptionsIntern(aopConversion: AesOptionConversion): GuideOptions {
return GuideOptions.NONE
}
}

private class LegendConfig internal constructor(opts: Map<String, Any>) : GuideConfig(opts) {

override fun createGuideOptionsIntern(): GuideOptions {
override fun createGuideOptionsIntern(aopConversion: AesOptionConversion): GuideOptions {
return LegendOptions(
colCount = getDouble(COL_COUNT)?.toInt()?.let { max(1, it) },
rowCount = getDouble(ROW_COUNT)?.toInt()?.let { max(1, it) },
byRow = getBoolean(BY_ROW)
byRow = getBoolean(BY_ROW),
overrideAesValues = initValues(
OptionsAccessor(getMap(OVERRIDE_AES)),
aopConversion
)
)
}

private fun initValues(
layerOptions: OptionsAccessor,
aopConversion: AesOptionConversion
): Map<Aes<*>, Any> {
val result = HashMap<Aes<*>, Any>()
Option.Mapping.REAL_AES_OPTION_NAMES
.filter(layerOptions::has)
.associateWith(Option.Mapping::toAes)
.forEach { (option, aes) ->
val optionValue = layerOptions.getSafe(option)
val value = if (optionValue is List<*>) {
optionValue.map { aopConversion.apply(aes, it) }
} else {
aopConversion.apply(aes, optionValue)
} ?: throw IllegalArgumentException("Can't convert to '$option' value: $optionValue")
result[aes] = value
}
return result
}
}

private class ColorBarConfig(opts: Map<String, Any>) : GuideConfig(opts) {

override fun createGuideOptionsIntern(): GuideOptions {
override fun createGuideOptionsIntern(aopConversion: AesOptionConversion): GuideOptions {
return ColorBarOptions(
width = getDouble(WIDTH),
height = getDouble(HEIGHT),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ class PlotConfigFrontend private constructor(
internal val yAxisPosition: AxisPosition

init {
guideOptionsMap = createGuideOptionsMap(this.scaleConfigs) + createGuideOptionsMap(getMap(GUIDES))
guideOptionsMap = createGuideOptionsMap(this.scaleConfigs, aopConversion) +
createGuideOptionsMap(getMap(GUIDES), aopConversion)

xAxisPosition = scaleProviderByAes.getValue(Aes.X).axisPosition
yAxisPosition = scaleProviderByAes.getValue(Aes.Y).axisPosition
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,26 @@ import org.jetbrains.letsPlot.core.spec.config.CoordConfig
import org.jetbrains.letsPlot.core.spec.config.GuideConfig
import org.jetbrains.letsPlot.core.spec.config.PlotConfigTransforms
import org.jetbrains.letsPlot.core.spec.config.ScaleConfig
import org.jetbrains.letsPlot.core.spec.conversion.AesOptionConversion
import org.jetbrains.letsPlot.core.spec.front.tiles.PlotTilesConfig

object PlotConfigFrontendUtil {
internal fun createGuideOptionsMap(scaleConfigs: List<ScaleConfig<*>>): Map<Aes<*>, GuideOptions> {
internal fun createGuideOptionsMap(scaleConfigs: List<ScaleConfig<*>>, aopConversion: AesOptionConversion): Map<Aes<*>, GuideOptions> {
val guideOptionsByAes = HashMap<Aes<*>, GuideOptions>()
for (scaleConfig in scaleConfigs) {
if (scaleConfig.hasGuideOptions()) {
val guideOptions = scaleConfig.getGuideOptions().createGuideOptions()
val guideOptions = scaleConfig.getGuideOptions().createGuideOptions(aopConversion)
guideOptionsByAes[scaleConfig.aes] = guideOptions
}
}
return guideOptionsByAes
}

internal fun createGuideOptionsMap(guideOptionsList: Map<String, Any>): Map<Aes<*>, GuideOptions> {
internal fun createGuideOptionsMap(guideOptionsList: Map<String, Any>, aopConversion: AesOptionConversion): Map<Aes<*>, GuideOptions> {
val guideOptionsByAes = HashMap<Aes<*>, GuideOptions>()
for ((key, value) in guideOptionsList) {
val aes = Option.Mapping.toAes(key)
guideOptionsByAes[aes] = GuideConfig.create(value).createGuideOptions()
guideOptionsByAes[aes] = GuideConfig.create(value).createGuideOptions(aopConversion)
}
return guideOptionsByAes
}
Expand Down
4 changes: 3 additions & 1 deletion python-package/lets_plot/plot/guide.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
__all__ = ['guide_legend', 'guide_colorbar', 'guides']


def guide_legend(nrow=None, ncol=None, byrow=None):
def guide_legend(nrow=None, ncol=None, byrow=None, override_aes=None):
"""
Legend guide.

Expand All @@ -19,6 +19,8 @@ def guide_legend(nrow=None, ncol=None, byrow=None):
Number of columns in legend's guide.
byrow : bool, default=True
Type of output: by row, or by column.
override_aes : list
A list of aesthetic parameters that will override the default legend appearance.

Returns
-------
Expand Down