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

coord_polar: axis, ticks, labels, xlim/ylim #979

Merged
merged 18 commits into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
c574810
Make expand additive
IKupriyanov-HORIS Dec 20, 2023
2467a22
coord_polar: reduce number of ticks for v axis
IKupriyanov-HORIS Dec 20, 2023
4fc32be
coord_polar: replace absolute expand with relative
IKupriyanov-HORIS Dec 20, 2023
4be95a0
coord_polar: v axis ticks alignment
IKupriyanov-HORIS Dec 20, 2023
e3d7535
coord_polar: hardcoded anchor for ticks
IKupriyanov-HORIS Dec 20, 2023
a9705f1
coord_polar: more ticks for circle axis
IKupriyanov-HORIS Dec 21, 2023
52151a7
coord_polar: fix horizontal axis ticks order, fix axis circle positio…
IKupriyanov-HORIS Dec 21, 2023
7af0732
coord_polar: fix horizontal axis first and last ticks overlapping
IKupriyanov-HORIS Dec 21, 2023
f6fd891
coord_polar: update notebook
IKupriyanov-HORIS Dec 22, 2023
0900649
coord_polar: replace thetaFromX with flipped
IKupriyanov-HORIS Dec 25, 2023
f54d197
coord_polar: temp fix for axis, need proper fix for the domain flip
IKupriyanov-HORIS Dec 26, 2023
62f8d02
coord_polar: properly handle theta=y in PolarCoordinateSystem
IKupriyanov-HORIS Dec 27, 2023
dc71ebc
coord_polar: handle theta=y mostly in PolarCoordinateSystem. Investig…
IKupriyanov-HORIS Dec 27, 2023
9c94803
coord_polar: fixed breaks and ticks. The only not working case is a Y…
IKupriyanov-HORIS Dec 28, 2023
a550e39
coord_polar: fixed breaks and ticks with start parameter
IKupriyanov-HORIS Dec 28, 2023
b99f9ea
coord_polar: minor code cleanup
IKupriyanov-HORIS Dec 29, 2023
09921c8
coord_polar: fix domain for discrete angle scale
IKupriyanov-HORIS Jan 1, 2024
0e4782f
coord_polar: add xlim/ylim for radar plot / lollipop
IKupriyanov-HORIS Jan 5, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ class DoubleRectangle(val origin: DoubleVector, val dimension: DoubleVector) {
)
}

fun flipIf(flipped: Boolean): DoubleRectangle {
return if (flipped) flip() else this
}

fun union(rect: DoubleRectangle): DoubleRectangle {
val newOrigin = origin.min(rect.origin)
val corner = origin.add(dimension)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ package org.jetbrains.letsPlot.commons.geometry
import kotlin.math.*

class DoubleVector(val x: Double, val y: Double) {
constructor(x: Number, y: Number) : this(x.toDouble(), y.toDouble())

operator fun component1(): Double = x
operator fun component2(): Double = y

Expand Down Expand Up @@ -63,6 +65,10 @@ class DoubleVector(val x: Double, val y: Double) {
return DoubleVector(y, x)
}

fun flipIf(flipped: Boolean): DoubleVector {
return if (flipped) flip() else this
}

override fun equals(other: Any?): Boolean {
if (other !is DoubleVector) {
return false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ open class PolygonWithCoordMapDemo : SimpleDemoBase() {
.alpha(constant(0.5))
.build()
val coord = CoordProviders.map().let {
val adjustedDomain = it.adjustDomain(DoubleRectangle(domainX, domainY))
val adjustedDomain = it.adjustDomain(DoubleRectangle(domainX, domainY), false)
it.createCoordinateSystem(
adjustedDomain = adjustedDomain,
clientSize = demoInnerSize
Expand Down
3,186 changes: 2,188 additions & 998 deletions docs/dev/notebooks/coord_polar.ipynb

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ import org.jetbrains.letsPlot.core.commons.data.SeriesUtil.pickAtIndices
import org.jetbrains.letsPlot.core.plot.base.CoordinateSystem
import org.jetbrains.letsPlot.core.plot.base.scale.ScaleBreaks
import org.jetbrains.letsPlot.core.plot.base.theme.AxisTheme
import org.jetbrains.letsPlot.core.plot.builder.coord.PolarCoordinateSystem
import org.jetbrains.letsPlot.core.plot.builder.guide.AxisComponent
import org.jetbrains.letsPlot.core.plot.builder.guide.Orientation
import org.jetbrains.letsPlot.core.plot.builder.layout.PlotLabelSpecFactory
import org.jetbrains.letsPlot.core.plot.builder.presentation.LabelSpec
import kotlin.math.PI
import kotlin.math.abs

object AxisUtil {
Expand All @@ -42,7 +44,8 @@ object AxisUtil {
val label = pair.first
val labelOffset = tickLabelBaseOffset.add(labelAdjustments.additionalOffset(i))

if (labelsMap.haveSpace(br, label, labelOffset)) {
val loc = if (orientation.isHorizontal) br.x else br.y
if (labelsMap.haveSpace(loc, label, labelOffset)) {
return@mapIndexedNotNull i
} else {
return@mapIndexedNotNull null
Expand All @@ -53,9 +56,7 @@ object AxisUtil {
majorClientBreaks.indices.toList()
}

val visibleMajorClientBreaks = pickAtIndices(majorClientBreaks, visibleBreaks)
val visibleMajorLabels = pickAtIndices(scaleBreaks.labels, visibleBreaks)

val visibleMajorDomainBreak = pickAtIndices(scaleBreaks.transformedValues, visibleBreaks)
val visibleMinorDomainBreak = if (visibleMajorDomainBreak.size > 1) {
val step = (visibleMajorDomainBreak[1] - visibleMajorDomainBreak[0])
Expand All @@ -65,25 +66,41 @@ object AxisUtil {
emptyList()
}

val visibleMinorClientBreaks = toClient(visibleMinorDomainBreak, domain, coord, flipAxis, orientation.isHorizontal)
val visibleMajorClientBreaks = pickAtIndices(majorClientBreaks, visibleBreaks)
.map { checkNotNull(it) { "Nulls are not allowed. Properly clean and sync breaks, grids and labels." } }

val visibleMinorClientBreaks =
toClient(visibleMinorDomainBreak, domain, coord, flipAxis, orientation.isHorizontal)
.map { checkNotNull(it) { "Nulls are not allowed. Properly clean and sync breaks, grids and labels." } }

val majorGrid = buildGrid(visibleMajorDomainBreak, domain, coord, flipAxis, orientation.isHorizontal)
val minorGrid = buildGrid(visibleMinorDomainBreak, domain, coord, flipAxis, orientation.isHorizontal)

// For coord_polar squash first and last labels into one to avoid overlapping.
val labels = if (visibleMajorClientBreaks.first().subtract(visibleMajorClientBreaks.last()).length() <= 3.0) {
val labels = visibleMajorLabels.toMutableList()
labels[labels.lastIndex] = "${labels[labels.lastIndex]}/${labels[0]}"
labels[0] = ""
labels
} else {
visibleMajorLabels
}

return AxisComponent.BreaksData(
majorBreaks = visibleMajorClientBreaks.filterNotNull(),
minorBreaks = visibleMinorClientBreaks.filterNotNull(),
majorLabels = visibleMajorLabels,
majorBreaks = visibleMajorClientBreaks,
minorBreaks = visibleMinorClientBreaks,
majorLabels = labels,
majorGrid = majorGrid,
minorGrid = minorGrid
)
}

private fun toClient(v: DoubleVector, coordinateSystem: CoordinateSystem, flipAxis: Boolean): DoubleVector? {
return finiteOrNull(coordinateSystem.toClient(if (flipAxis) v.flip() else v))
return finiteOrNull(coordinateSystem.toClient(v.flipIf(flipAxis)))
}

private fun toClient(v: DoubleRectangle, coordinateSystem: CoordinateSystem, flipAxis: Boolean): DoubleRectangle? {
return coordinateSystem.toClient(if (flipAxis) v.flip() else v)
return coordinateSystem.toClient(v.flipIf(flipAxis))
}

private fun toClient(
Expand All @@ -92,20 +109,34 @@ object AxisUtil {
coordinateSystem: CoordinateSystem,
flipAxis: Boolean,
horizontal: Boolean
): List<Double?> {
return breaks.map { breakValue ->
val breakCoord = when (horizontal) {
true -> DoubleVector(breakValue, domain.yRange().lowerEnd)
false -> DoubleVector(domain.xRange().lowerEnd, breakValue)
): List<DoubleVector?> {
return if (coordinateSystem is PolarCoordinateSystem) {

val startAnglePercent = (coordinateSystem.startAngle % (2 * PI)) / (2 * PI)
val startAngleOffset = domain.xRange().length * startAnglePercent
val verticalAngleValue = (domain.xRange().lowerEnd - startAngleOffset).let {
when { // non-normalized domain value
it < domain.xRange().lowerEnd -> it + domain.xRange().length
it > domain.xRange().upperEnd -> it - domain.xRange().length
else -> it
}
}

val breakClientCoord = toClient(breakCoord, coordinateSystem, flipAxis) ?: return@map null

when (horizontal) {
true -> breakClientCoord.x
false -> breakClientCoord.y
breaks.map { breakValue ->
when (horizontal) {
true -> DoubleVector(breakValue, domain.yRange().upperEnd)
false -> DoubleVector(verticalAngleValue, breakValue)
}
}
} else {
breaks.map { breakValue ->
when (horizontal) {
true -> DoubleVector(breakValue, domain.yRange().upperEnd)
false -> DoubleVector(domain.xRange().lowerEnd, breakValue)
}
}
}
.map { toClient(it, coordinateSystem, flipAxis) ?: return@map null }
}

private fun buildGrid(
Expand Down Expand Up @@ -198,11 +229,7 @@ object AxisUtil {
labelOffset: DoubleVector
): DoubleRectangle {
val labelNormalSize = labelSpec.dimensions(label)
val wh = if (isVertical(rotationDegree)) {
labelNormalSize.flip()
} else {
labelNormalSize
}
val wh = labelNormalSize.flipIf(isVertical(rotationDegree))
val origin = if (horizontalAxis) DoubleVector(loc, 0.0) else DoubleVector(0.0, loc)
return DoubleRectangle(origin, wh)
.subtract(wh.mul(0.5)) // labels use central adjustments
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import org.jetbrains.letsPlot.core.plot.builder.GeomLayer
import org.jetbrains.letsPlot.core.plot.builder.MarginalLayerUtil
import org.jetbrains.letsPlot.core.plot.builder.PlotSvgComponent
import org.jetbrains.letsPlot.core.plot.builder.coord.CoordProvider
import org.jetbrains.letsPlot.core.plot.builder.coord.PolarCoordProvider
import org.jetbrains.letsPlot.core.plot.builder.frame.BogusFrameOfReferenceProvider
import org.jetbrains.letsPlot.core.plot.builder.frame.SquareFrameOfReferenceProvider
import org.jetbrains.letsPlot.core.plot.builder.layout.GeomMarginsLayout
Expand Down Expand Up @@ -97,8 +98,9 @@ class PlotAssembler constructor(

val flipAxis = coordProvider.flipped

val (hAxisPosition, vAxisPosition) = when (flipAxis) {
true -> yAxisPosition.flip() to xAxisPosition.flip()
val (hAxisPosition, vAxisPosition) = when {
coordProvider is PolarCoordProvider -> AxisPosition.BOTTOM to AxisPosition.LEFT
flipAxis -> yAxisPosition.flip() to xAxisPosition.flip()
else -> xAxisPosition to yAxisPosition
}

Expand Down Expand Up @@ -245,7 +247,7 @@ class PlotAssembler constructor(

// Create frame of reference provider for each tile.
return domainsXYByTile.map { (xDomain, yDomain) ->
val adjustedDomain = coordProvider.adjustDomain(DoubleRectangle(xDomain, yDomain))
val adjustedDomain = coordProvider.adjustDomain(DoubleRectangle(xDomain, yDomain), hScaleProto.isContinuous)
SquareFrameOfReferenceProvider(
hScaleProto, vScaleProto,
adjustedDomain,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ interface CoordProvider {
/**
* Reshape and flip the domain if necessary.
*/
fun adjustDomain(domain: DoubleRectangle): DoubleRectangle
fun adjustDomain(domain: DoubleRectangle, isHScaleContinuous: Boolean): DoubleRectangle

fun adjustGeomSize(
hDomain: DoubleSpan,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import org.jetbrains.letsPlot.commons.interval.DoubleSpan
import org.jetbrains.letsPlot.core.plot.base.coord.CoordinatesMapper

internal abstract class CoordProviderBase(
private val xLim: DoubleSpan?,
private val yLim: DoubleSpan?,
protected val xLim: DoubleSpan?,
protected val yLim: DoubleSpan?,
override val flipped: Boolean,
protected val projection: Projection = identity(),
) : CoordProvider {
Expand All @@ -29,7 +29,7 @@ internal abstract class CoordProviderBase(
/**
* Reshape and flip the domain if necessary.
*/
override fun adjustDomain(domain: DoubleRectangle): DoubleRectangle {
override fun adjustDomain(domain: DoubleRectangle, isHScaleContinuous: Boolean): DoubleRectangle {
val validDomain = domain.let {
val withLims = DoubleRectangle(
xLim ?: domain.xRange(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,13 @@ object CoordProviders {
)
}

fun polar(thetaFromX: Boolean, start: Double, clockwise: Boolean): CoordProvider {
return PolarCoordProvider(thetaFromX, start, clockwise)
fun polar(
xLim: DoubleSpan? = null,
yLim: DoubleSpan? = null,
flipped: Boolean,
start: Double,
clockwise: Boolean
): CoordProvider {
return PolarCoordProvider(xLim, yLim, flipped, start, clockwise)
}
}
Loading