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

Refactor boxplot #790

Merged
merged 19 commits into from
Jun 8, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
67c2fb4
Refactor boxplot jfx demo.
ASmirnov-HORIS May 12, 2023
2850b43
First step of the geom_boxplot() splitting.
ASmirnov-HORIS May 12, 2023
3c28d43
Merge branch 'master' into refactor-boxplot
ASmirnov-HORIS May 19, 2023
b8bc104
Fixes after merging with master.
ASmirnov-HORIS May 19, 2023
45e3d06
Merge branch 'master' into refactor-boxplot
ASmirnov-HORIS May 31, 2023
8223c76
Fix outlier positions when there is additional grouping.
ASmirnov-HORIS May 31, 2023
1664b30
Add geometry for the boxplot outlier.
ASmirnov-HORIS May 31, 2023
3d961ed
Update Python function for the geom_boxplot().
ASmirnov-HORIS Jun 1, 2023
6bc8ecd
Remove Aes.Y from GeomMeta for the boxplot geometry.
ASmirnov-HORIS Jun 2, 2023
45d8d4a
Remove the BoxplotOutlierGeom and connected code.
ASmirnov-HORIS Jun 5, 2023
26c4f5f
Remove sampling parameter from API of the geom_boxplot() function.
ASmirnov-HORIS Jun 5, 2023
28fe0c1
Merge branch 'master' into refactor-boxplot
ASmirnov-HORIS Jun 5, 2023
98915a1
Small improvements of code in the BoxplotStat.
ASmirnov-HORIS Jun 6, 2023
fec263d
Tiny fix in the BoxplotStat.
ASmirnov-HORIS Jun 6, 2023
50359f3
Small fixes of boxplot in different places.
ASmirnov-HORIS Jun 6, 2023
04d9567
Merge branch 'master' into refactor-boxplot
ASmirnov-HORIS Jun 6, 2023
be190f3
Remove extra mentions of an outliers.
ASmirnov-HORIS Jun 7, 2023
9333e0a
Increase default value of the midline fatten for the boxplot geometry.
ASmirnov-HORIS Jun 7, 2023
475ac6c
Add dodge width to default position for the outlier geometry.
ASmirnov-HORIS Jun 7, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Merge branch 'master' into refactor-boxplot
# Conflicts:
#	plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/GeomProtoClientSide.kt
  • Loading branch information
ASmirnov-HORIS committed May 19, 2023
commit 3c28d439c888bfa57c7083db2804279dd9fa815b
2 changes: 2 additions & 0 deletions future_changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@
### Changed

### Fixed
- ggsave: saving geomImshow() to SVG produces fuzzy picture [[LPK-188](https://github.com/JetBrains/lets-plot-kotlin/issues/188)].
- ggsave: saving geomImshow() to raster format produces fuzzy picture.
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,7 @@ import jetbrains.datalore.plot.base.render.SvgRoot

interface Geom {
val legendKeyElementFactory: LegendKeyElementFactory
val wontRender: List<Aes<*>> get() = emptyList()
fun rangeIncludesZero(aes: Aes<*>): Boolean = false
fun build(root: SvgRoot, aesthetics: Aesthetics, pos: PositionAdjustment, coord: CoordinateSystem, ctx: GeomContext)
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,13 @@ object GeomMeta {
return renderedAesByGeom[geomKind]!!
}

fun renders(geomKind: GeomKind, actualColorAes: Aes<Color>, actualFillAes: Aes<Color>): List<Aes<*>> {
return renders(geomKind).map {
fun renders(
geomKind: GeomKind,
actualColorAes: Aes<Color>,
actualFillAes: Aes<Color>,
exclude: List<Aes<*>> = emptyList()
): List<Aes<*>> {
return (renders(geomKind) - exclude).map {
when (it) {
Aes.COLOR -> actualColorAes
Aes.FILL -> actualFillAes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,6 @@ open class AestheticsDefaults {
return this
}


open fun rangeIncludesZero(aes: Aes<*>): Boolean {
return false
}

fun <T> defaultValue(aes: Aes<T>): T {
return myDefaults[aes]
}
Expand Down Expand Up @@ -82,21 +77,13 @@ open class AestheticsDefaults {
}

fun bar(): AestheticsDefaults {
return object : AestheticsDefaults() {
override fun rangeIncludesZero(aes: Aes<*>): Boolean {
return aes == Aes.Y || super.rangeIncludesZero(aes)
}
}
return base()
.update(Aes.WIDTH, 0.9)
.update(Aes.COLOR, Color.TRANSPARENT) // no outline (transparent)
}

fun histogram(): AestheticsDefaults {
return object : AestheticsDefaults() {
override fun rangeIncludesZero(aes: Aes<*>): Boolean {
return aes == Aes.Y || super.rangeIncludesZero(aes)
}
}
return base()
.update(Aes.COLOR, Color.TRANSPARENT) // no outline (transparent)
}

Expand Down Expand Up @@ -178,11 +165,7 @@ open class AestheticsDefaults {
}

fun area(): AestheticsDefaults {
return object : AestheticsDefaults() {
override fun rangeIncludesZero(aes: Aes<*>): Boolean {
return aes == Aes.Y || super.rangeIncludesZero(aes)
}
}
return base()
}

fun density(): AestheticsDefaults {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ open class AreaGeom : GeomBase() {
var quantiles: List<Double> = DensityStat.DEF_QUANTILES
var quantileLines: Boolean = DEF_QUANTILE_LINES

override fun rangeIncludesZero(aes: Aes<*>): Boolean = (aes == Aes.Y)

protected fun dataPoints(aesthetics: Aesthetics): Iterable<DataPointAesthetics> {
return GeomUtil.ordered_X(aesthetics.dataPoints())
}
Expand All @@ -35,25 +37,26 @@ open class AreaGeom : GeomBase() {
val helper = LinesHelper(pos, coord, ctx)
val quantilesHelper = QuantilesHelper(pos, coord, ctx, quantiles)
val dataPoints = GeomUtil.withDefined(aesthetics.dataPoints(), Aes.X, Aes.Y)
dataPoints.sortedByDescending(DataPointAesthetics::group).groupBy(DataPointAesthetics::group).forEach { (_, groupDataPoints) ->
quantilesHelper.splitByQuantiles(groupDataPoints, Aes.X).forEach { points ->
val paths = helper.createBands(points, GeomUtil.TO_LOCATION_X_Y, GeomUtil.TO_LOCATION_X_ZERO)
// If you want to retain the side edges of area: comment out the following codes,
// and switch decorate method in LinesHelper.createBands
appendNodes(paths, root)

helper.setAlphaEnabled(false)
appendNodes(helper.createLines(points, GeomUtil.TO_LOCATION_X_Y), root)

buildHints(points, pos, coord, ctx)
}
dataPoints.sortedByDescending(DataPointAesthetics::group).groupBy(DataPointAesthetics::group)
.forEach { (_, groupDataPoints) ->
quantilesHelper.splitByQuantiles(groupDataPoints, Aes.X).forEach { points ->
val paths = helper.createBands(points, GeomUtil.TO_LOCATION_X_Y, GeomUtil.TO_LOCATION_X_ZERO)
// If you want to retain the side edges of area: comment out the following codes,
// and switch decorate method in LinesHelper.createBands
appendNodes(paths, root)

helper.setAlphaEnabled(false)
appendNodes(helper.createLines(points, GeomUtil.TO_LOCATION_X_Y), root)

buildHints(points, pos, coord, ctx)
}

if (quantileLines) {
createQuantileLines(groupDataPoints, quantilesHelper).forEach { quantileLine ->
root.add(quantileLine)
if (quantileLines) {
createQuantileLines(groupDataPoints, quantilesHelper).forEach { quantileLine ->
root.add(quantileLine)
}
}
}
}
}

private fun createQuantileLines(
Expand All @@ -69,7 +72,12 @@ open class AreaGeom : GeomBase() {
return quantilesHelper.getQuantileLineElements(dataPoints, Aes.X, toLocationBoundStart, toLocationBoundEnd)
}

private fun buildHints(dataPoints: Iterable<DataPointAesthetics>, pos: PositionAdjustment, coord: CoordinateSystem, ctx: GeomContext) {
private fun buildHints(
dataPoints: Iterable<DataPointAesthetics>,
pos: PositionAdjustment,
coord: CoordinateSystem,
ctx: GeomContext
) {
val geomHelper = GeomHelper(pos, coord, ctx)
val multiPointDataList = MultiPointDataConstructor.createMultiPointDataByGroup(
dataPoints,
Expand Down Expand Up @@ -108,20 +116,7 @@ open class AreaGeom : GeomBase() {
}

companion object {
// val RENDERS = listOf(
// Aes.X,
// Aes.Y,
// Aes.SIZE,
// Aes.LINETYPE,
// Aes.COLOR,
// Aes.FILL,
// Aes.ALPHA
// )

const val DEF_QUANTILE_LINES = false

const val HANDLES_GROUPS = true
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import jetbrains.datalore.plot.common.data.SeriesUtil

open class BarGeom : GeomBase() {

override fun rangeIncludesZero(aes: Aes<*>): Boolean = (aes == Aes.Y)

override fun buildIntern(
root: SvgRoot,
aesthetics: Aesthetics,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,7 @@ class DensityGeom : AreaGeom() {
}

companion object {
// val RENDERS: List<Aes<*>> = AreaGeom.RENDERS
const val DEF_QUANTILE_LINES =
AreaGeom.DEF_QUANTILE_LINES

const val HANDLES_GROUPS =
AreaGeom.HANDLES_GROUPS
const val DEF_QUANTILE_LINES = AreaGeom.DEF_QUANTILE_LINES
const val HANDLES_GROUPS = AreaGeom.HANDLES_GROUPS
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,20 @@ import jetbrains.datalore.plot.base.render.SvgRoot
import jetbrains.datalore.vis.svg.SvgGElement
import jetbrains.datalore.vis.svg.SvgLineElement

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

override val legendKeyElementFactory: LegendKeyElementFactory
get() = ErrorBarLegendKeyElementFactory()

override val wontRender: List<Aes<*>>
get() {
return if (isVertical) {
listOf(Aes.Y, Aes.XMIN, Aes.XMAX, Aes.HEIGHT)
} else {
listOf(Aes.X, Aes.YMIN, Aes.YMAX, Aes.WIDTH)
}
}

override fun buildIntern(
root: SvgRoot,
aesthetics: Aesthetics,
Expand All @@ -38,17 +47,13 @@ class ErrorBarGeom : GeomBase() {
val geomHelper = GeomHelper(pos, coord, ctx)
val colorsByDataPoint = HintColorUtil.createColorMarkerMapper(GeomKind.ERROR_BAR, ctx)

var dataPoints = GeomUtil.withDefined(aesthetics.dataPoints(), Aes.X, Aes.YMIN, Aes.YMAX, Aes.WIDTH)
val isVertical = dataPoints.any()
if (!isVertical) {
dataPoints = GeomUtil.withDefined(aesthetics.dataPoints(), Aes.Y, Aes.XMIN, Aes.XMAX, Aes.HEIGHT)
}

val xAes = if (isVertical) Aes.X else Aes.Y
val minAes = if (isVertical) Aes.YMIN else Aes.XMIN
val maxAes = if (isVertical) Aes.YMAX else Aes.XMAX
val widthAes = if (isVertical) Aes.WIDTH else Aes.HEIGHT

val dataPoints = GeomUtil.withDefined(aesthetics.dataPoints(), xAes, minAes, maxAes, widthAes)

for (p in dataPoints) {
val x = p[xAes]!!
val ymin = p[minAes]!!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

package jetbrains.datalore.plot.base.geom

import jetbrains.datalore.base.interval.DoubleSpan
import jetbrains.datalore.base.geometry.DoubleRectangle
import jetbrains.datalore.base.interval.DoubleSpan
import jetbrains.datalore.plot.base.*
import jetbrains.datalore.plot.base.geom.legend.GenericLegendKeyElementFactory
import jetbrains.datalore.plot.base.interact.GeomTargetCollector
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,6 @@ package jetbrains.datalore.plot.base.geom

class HistogramGeom : BarGeom() {
companion object {
// val RENDERS = listOf(
// Aes.X,
// Aes.Y,
// Aes.COLOR,
// Aes.FILL,
// Aes.ALPHA,
// //Aes.WEIGHT, // ToDo: this is actually handled by 'stat' (bin,count)
// Aes.WIDTH,
// Aes.SIZE
// )

const val HANDLES_GROUPS = false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import jetbrains.datalore.plot.base.render.LegendKeyElementFactory
import jetbrains.datalore.plot.base.render.SvgRoot


class LiveMapGeom: Geom {
class LiveMapGeom : Geom {
private lateinit var myMapProvider: LiveMapProvider

override val legendKeyElementFactory: LegendKeyElementFactory
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import jetbrains.datalore.base.values.Color
import jetbrains.datalore.plot.base.*
import jetbrains.datalore.plot.base.aes.AesScaling
import jetbrains.datalore.plot.base.geom.legend.LollipopLegendKeyElementFactory
import jetbrains.datalore.plot.base.geom.util.*
import jetbrains.datalore.plot.base.geom.util.GeomHelper
import jetbrains.datalore.plot.base.geom.util.GeomUtil
import jetbrains.datalore.plot.base.geom.util.HintColorUtil
import jetbrains.datalore.plot.base.interact.GeomTargetCollector
import jetbrains.datalore.plot.base.render.LegendKeyElementFactory
import jetbrains.datalore.plot.base.render.SvgRoot
Expand All @@ -32,6 +34,14 @@ class LollipopGeom : GeomBase(), WithWidth, WithHeight {
override val legendKeyElementFactory: LegendKeyElementFactory
get() = LollipopLegendKeyElementFactory(fatten)

override fun rangeIncludesZero(aes: Aes<*>): Boolean {
// Pin the lollipops to an axis when baseline coincides with this axis and sticks are perpendicular to it
return aes == Aes.Y
&& slope == 0.0
&& intercept == 0.0
&& direction != Direction.ALONG_AXIS
}

override fun buildIntern(
root: SvgRoot,
aesthetics: Aesthetics,
Expand Down Expand Up @@ -121,6 +131,7 @@ class LollipopGeom : GeomBase(), WithWidth, WithHeight {
DoubleVector((head.y - intercept) / slope, head.y)
}
}

Direction.SLOPE -> {
val baseX = (head.x + slope * (head.y - intercept)) / (1 + slope.pow(2))
val baseY = slope * baseX + intercept
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ package jetbrains.datalore.plot.base.stat

import jetbrains.datalore.plot.base.Aes
import jetbrains.datalore.plot.base.DataFrame
import jetbrains.datalore.plot.base.StatContext

/**
* Counts the number of cases at each x position
Expand All @@ -19,13 +18,6 @@ internal class CountStat : AbstractCountStat(DEF_MAPPING, count2d = false) {
return listOf(Aes.X, Aes.WEIGHT)
}

override fun apply(data: DataFrame, statCtx: StatContext, messageConsumer: (s: String) -> Unit): DataFrame {
if (!hasRequiredValues(data, Aes.X)) {
return withEmptyStatValues()
}
return super.apply(data, statCtx, messageConsumer)
}

override fun addToStatVars(values: Set<Any>): Map<DataFrame.Variable, List<Double>> {
val statX = values.map { it as Double }
return mapOf(Stats.X to statX)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,27 +197,18 @@ class SmoothStat constructor(
Method.LM -> {
require(polynomialDegree >= 1) { "Degree of polynomial regression must be at least 1" }
if (polynomialDegree == 1) {
LinearRegression(valuesX, valuesY, confidenceLevel)
LinearRegression.fit(valuesX, valuesY, confidenceLevel)
} else {
if (PolynomialRegression.canBeComputed(valuesX, valuesY, polynomialDegree)) {
PolynomialRegression(valuesX, valuesY, confidenceLevel, polynomialDegree)
} else {
return result // empty stat data
}
PolynomialRegression.fit(valuesX, valuesY, confidenceLevel, polynomialDegree)
}
}
Method.LOESS -> {
val evaluator = LocalPolynomialRegression(valuesX, valuesY, confidenceLevel, span)
if (evaluator.canCompute) {
evaluator
} else {
return result // empty stat data
}
LocalPolynomialRegression.fit(valuesX, valuesY, confidenceLevel, span)
}
else -> throw IllegalArgumentException(
"Unsupported smoother method: $smoothingMethod (only 'lm' and 'loess' methods are currently available)"
)
}
} ?: return result

val rangeX = SeriesUtil.range(valuesX) ?: return result

Expand Down
Loading
You are viewing a condensed version of this merge commit. You can view the full changes here.