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

Fix lims for FixedCoordProvider #83

Merged
merged 3 commits into from
Feb 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -29,4 +29,7 @@ class Pair<FirstT, SecondT>(val first: FirstT, val second: SecondT) {
override fun toString(): String {
return "[$first, $second]"
}

operator fun component1() = first
operator fun component2() = second
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,19 +54,14 @@ internal abstract class CoordProviderBase(
displaySize: DoubleVector
): Pair<ClosedRange<Double>, ClosedRange<Double>> {
return adjustDomainsImpl(xDomain, yDomain, displaySize)
.let { Pair(
xLim ?: it.first,
yLim ?: it.second
)
}
}

protected open fun adjustDomainsImpl(
xDomain: ClosedRange<Double>,
yDomain: ClosedRange<Double>,
displaySize: DoubleVector
): Pair<ClosedRange<Double>, ClosedRange<Double>> {
return Pair(xDomain, yDomain)
return Pair(xLim ?: xDomain, yLim ?: yDomain)
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import jetbrains.datalore.base.gcommon.collect.ClosedRange
import jetbrains.datalore.base.geometry.DoubleVector
import jetbrains.datalore.base.values.Pair
import jetbrains.datalore.plot.common.data.SeriesUtil
import jetbrains.datalore.plot.common.data.SeriesUtil.span

/**
* A fixed scale coordinate system forces a specified ratio between the physical representation of data units on the axes.
Expand All @@ -27,17 +28,6 @@ internal open class FixedRatioCoordProvider(
yDomain: ClosedRange<Double>,
displaySize: DoubleVector
): Pair<ClosedRange<Double>, ClosedRange<Double>> {
@Suppress("NAME_SHADOWING")
var xDomain = xDomain
@Suppress("NAME_SHADOWING")
var yDomain = yDomain

val spanX = SeriesUtil.span(xDomain)
val spanY = SeriesUtil.span(yDomain)
if (spanX < SeriesUtil.TINY || spanY < SeriesUtil.TINY) {
return Pair(xDomain, yDomain) // don't touch
}

// fit the data into the display
var displayW = displaySize.x
var displayH = displaySize.y
Expand All @@ -52,19 +42,61 @@ internal open class FixedRatioCoordProvider(
displayH *= 1 / myRatio
}

val ratioX = spanX / displayW
val ratioY = spanY / displayH
if (
listOf(xLim, yLim).all { it == null } ||
listOf(xLim, yLim).all { it != null }
) {
@Suppress("NAME_SHADOWING")
var xDomain = xDomain
@Suppress("NAME_SHADOWING")
var yDomain = yDomain

// Take bigger ratio and apply to ortogonal domain (axis) so that
// ratio: (data range) / (axis length) is the same for both X and Y.
if (ratioX > ratioY) {
val spanAdjusted = displayH * ratioX
yDomain = SeriesUtil.expand(yDomain, spanAdjusted)
} else {
val spanAdjusted = displayW * ratioY
xDomain = SeriesUtil.expand(xDomain, spanAdjusted)
if (listOf(xLim, yLim).all { it != null }) {
xDomain = xLim!!
yDomain = yLim!!
}

val spanX = span(xDomain)
val spanY = span(yDomain)
if (spanX < SeriesUtil.TINY || spanY < SeriesUtil.TINY) {
return Pair(xDomain, yDomain) // don't touch
}

val ratioX = spanX / displayW
val ratioY = spanY / displayH

// Take bigger ratio and apply to ortogonal domain (axis) so that
// ratio: (data range) / (axis length) is the same for both X and Y.
if (ratioX > ratioY) {
val spanAdjusted = displayH * ratioX
yDomain = SeriesUtil.expand(yDomain, spanAdjusted)
} else {
val spanAdjusted = displayW * ratioY
xDomain = SeriesUtil.expand(xDomain, spanAdjusted)
}

return Pair(xDomain, yDomain)
}

fun limitOrth(orig: ClosedRange<Double>, lim: ClosedRange<Double>, orth: ClosedRange<Double>):ClosedRange<Double> {
val scale = span(orig) / span(orth)
val lowerExpand = (orig.lowerEndpoint() - lim.lowerEndpoint()) * scale
val upperExpand = (lim.upperEndpoint() - orig.upperEndpoint()) * scale
return SeriesUtil.expand(orth, lowerExpand, upperExpand)
}

return Pair(xDomain, yDomain)
if (xLim != null) {
val newSpan = displayH * (span(xLim) / displayW)
val yLim = limitOrth(xDomain, xLim, yDomain)
return Pair(xLim, SeriesUtil.expand(yLim, newSpan))

} else if (yLim != null) {
val newSpan = displayW * (span(yLim) / displayH)
val xLim = limitOrth(yDomain, yLim, xDomain)
return Pair(SeriesUtil.expand(xLim, newSpan), yLim)

} else {
error("Impossible")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ internal class CoordCartesianTest : jetbrains.datalore.plot.builder.coord.CoordT

@Test
fun adjustDomains() {
val dataBounds = dataBounds!!
val dataBounds = dataBounds
// domains not changed
tryAdjustDomains(2.0,
PROVIDER, dataBounds.xRange(), dataBounds.yRange())
Expand Down
121 changes: 116 additions & 5 deletions plot-builder/src/jvmTest/kotlin/plot/builder/coord/CoordFixedTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,122 @@

package jetbrains.datalore.plot.builder.coord

import jetbrains.datalore.base.gcommon.collect.ClosedRange
import jetbrains.datalore.base.gcommon.collect.ClosedRange.Companion.closed
import jetbrains.datalore.base.geometry.DoubleRectangle
import jetbrains.datalore.base.geometry.DoubleVector
import jetbrains.datalore.base.values.Pair
import kotlin.math.abs
import kotlin.math.min
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.fail

internal class CoordFixedTest : jetbrains.datalore.plot.builder.coord.CoordTestBase() {

@BeforeTest
fun setUp() {
dataBounds = DoubleRectangle(DoubleVector.ZERO,
DATA_SPAN
)
dataBounds = DoubleRectangle(DoubleVector.ZERO, DATA_SPAN)
}

@Test
fun limits() {
fun squareCoord(xLim: ClosedRange<Double>? = null, yLim: ClosedRange<Double>? = null): Pair<ClosedRange<Double>, ClosedRange<Double>> {
val dataBounds = DoubleRectangle(DoubleVector.ZERO, DoubleVector(25.0, 25.0))
val screenSize = DoubleVector(800.0, 600.0)
val (xDomain, yDomain) = CoordProviders
.fixed(1.0, xLim, yLim)
.adjustDomains(dataBounds.xRange(), dataBounds.yRange(), screenSize)
return Pair(xDomain, yDomain)
}

// xLim
run {

// xLim in range
squareCoord(xLim = closed(1.0, 4.0))
.let { (xDomain, yDomain) ->
assertEquals(closed(1.0, 4.0), xDomain)
assertEquals(closed(1.375, 3.625), yDomain)
}

// xLim wider than range
squareCoord(xLim = closed(-3.0, 4.0))
.let { (xDomain, yDomain) ->
assertEquals(closed(-3.0, 4.0), xDomain)
assertEquals(closed(-2.125, 3.125), yDomain)
}

// xLim out of range
squareCoord(xLim = closed(-9.0, -4.0))
.let { (xDomain, yDomain) ->
assertEquals(closed(-9.0, -4.0), xDomain)
assertEquals(closed(-8.375, -4.625), yDomain)
}

}

// yLim
run {
// yLim in range
squareCoord(yLim = closed(1.0, 4.0))
.let { (xDomain, yDomain) ->
assertEquals(closed(0.5, 4.5), xDomain)
assertEquals(closed(1.0, 4.0), yDomain)
}

// yLim wider than range
squareCoord(yLim = closed(-3.0, 6.0))
.let { (xDomain, yDomain) ->
assertEquals(closed(-4.5, 7.5), xDomain)
assertEquals(closed(-3.0, 6.0), yDomain)
}

// yLim out of range
squareCoord(yLim = closed(-9.0, -6.0))
.let { (xDomain, yDomain) ->
assertEquals(closed(-9.5, -5.5), xDomain)
assertEquals(closed(-9.0, -6.0), yDomain)
}
}


// xLim && yLim
run {
// intersecting, wider yLim wins
squareCoord(xLim = closed(10.0, 15.0), yLim = closed(9.0, 16.0))
.let { (xDomain, yDomain) ->
assertEquals(closed(7.833, 17.166), xDomain)
assertEquals(closed(9.0, 16.0), yDomain)
}

// intersecting, wider xLim wins
squareCoord(xLim = closed(9.0, 16.0), yLim = closed(12.0, 14.0))
.let { (xDomain, yDomain) ->
assertEquals(closed(9.0, 16.0), xDomain)
assertEquals(closed(10.375, 15.625), yDomain)
}

// non-intersecting, wider xLim wins
squareCoord(xLim = closed(9.0, 16.0), yLim = closed(19.0, 22.0))
.let { (xDomain, yDomain) ->
assertEquals(closed(9.0, 16.0), xDomain)
assertEquals(closed(17.875, 23.125), yDomain)
}

// non-intersecting, wider yLim wins
squareCoord(xLim = closed(15.0, 16.0), yLim = closed(18.0, 22.0))
.let { (xDomain, yDomain) ->
assertEquals(closed(12.833, 18.166), xDomain)
assertEquals(closed(18.0, 22.0), yDomain)
}
}
}

@Test
fun adjustDomains() {
// fixed ratio == 1 (equal X and Y)
val dataBounds = dataBounds!!
val dataBounds = dataBounds
val rangeX = dataBounds.xRange()
val rangeY = dataBounds.yRange()

Expand Down Expand Up @@ -104,7 +202,20 @@ internal class CoordFixedTest : jetbrains.datalore.plot.builder.coord.CoordTestB

private fun shortSideOfDisplay(ratio: Double): Double {
val displaySize = unitDisplaySize(ratio)
return Math.min(displaySize.x, displaySize.y)
return min(displaySize.x, displaySize.y)
}

private fun ClosedRange<Double>.equals(other: ClosedRange<Double>, epsilon: Double = 0.00001): Boolean {
fun doubleEquals(expected: Double, actual: Double, epsilon: Double) = abs(expected - actual) < epsilon

return doubleEquals(lowerEndpoint(), other.lowerEndpoint(), epsilon) &&
doubleEquals(upperEndpoint(), other.upperEndpoint(), epsilon)
}

private fun assertEquals(expected: ClosedRange<Double>, actual: ClosedRange<Double>, epsilon: Double = 0.001) {
if (!expected.equals(actual, epsilon)) {
fail("$expected != $actual")
}
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package jetbrains.datalore.plot.builder.coord

import jetbrains.datalore.base.geometry.DoubleRectangle
import jetbrains.datalore.base.geometry.DoubleVector
import kotlin.math.min
import kotlin.test.BeforeTest
import kotlin.test.Test

Expand All @@ -24,7 +25,7 @@ internal class CoordMapTest : jetbrains.datalore.plot.builder.coord.CoordTestBas
// Coord Map keeps fixed ratio == 1 (equal X and Y)
val dataBounds = dataBounds
tryAdjustDomains(2.0,
PROVIDER, dataBounds!!.xRange(),
PROVIDER, dataBounds.xRange(),
expand(dataBounds.yRange(), 2.0)
)
tryAdjustDomains(0.5,
Expand All @@ -51,7 +52,7 @@ internal class CoordMapTest : jetbrains.datalore.plot.builder.coord.CoordTestBas

private fun shortSideOfDisplay(ratio: Double): Double {
val displaySize = unitDisplaySize(ratio)
return Math.min(displaySize.x, displaySize.y)
return min(displaySize.x, displaySize.y)
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ import kotlin.test.assertEquals

internal open class CoordTestBase {

var dataBounds: DoubleRectangle? = null
lateinit var dataBounds: DoubleRectangle

/**
* ratio - ratio between height and width of the display (ratio = h / w)
*/
fun tryAdjustDomains(ratio: Double, provider: CoordProvider, expectedX: ClosedRange<Double>, expectedY: ClosedRange<Double>) {

val dataBounds = this.dataBounds
val domainX = dataBounds!!.xRange()
val domainX = dataBounds.xRange()
val domainY = dataBounds.yRange()
val displaySize = unitDisplaySize(ratio)

Expand All @@ -40,7 +40,7 @@ internal open class CoordTestBase {
fun tryApplyScales(ratio: Double, provider: CoordProvider, expectedMin: DoubleVector, expectedMax: DoubleVector, accuracy: DoubleVector) {

val dataBounds = this.dataBounds
var domainX = dataBounds!!.xRange()
var domainX = dataBounds.xRange()
var domainY = dataBounds.yRange()
val displaySize = unitDisplaySize(ratio)
val domains = provider.adjustDomains(domainX, domainY, displaySize)
Expand Down
Loading