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 1 commit
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
Prev Previous commit
Lims for FixedRatioCoordProvider
  • Loading branch information
IKupriyanov-HORIS committed Feb 12, 2020
commit ba7fa8f50e9fd6507335de63ca8e561e7680d698
Original file line number Diff line number Diff line change
Expand Up @@ -42,54 +42,61 @@ internal open class FixedRatioCoordProvider(
displayH *= 1 / myRatio
}

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

@Suppress("NAME_SHADOWING")
var xDomain = xDomain
@Suppress("NAME_SHADOWING")
var yDomain = yDomain
if (listOf(xLim, yLim).all { it != null }) {
xDomain = xLim!!
yDomain = yLim!!
}

if (xLim != null && yLim != null) {
error("Not implementer; xLim != null && yLim != null")
}
else if (xLim == null && yLim == null) {
// do nothing
} else {
if (xLim != null) {
val spanX = span(xDomain)
val xLimStartRatio = xLim.lowerEndpoint() / spanX
val xLimEndRatio = xLim.upperEndpoint() / spanX
xDomain = xLim
val spanX = span(xDomain)
val spanY = span(yDomain)
if (spanX < SeriesUtil.TINY || spanY < SeriesUtil.TINY) {
return Pair(xDomain, yDomain) // don't touch
}

val yDomainSpan = span(yDomain)
yDomain = ClosedRange.closed(yDomain.lowerEndpoint() + yDomainSpan * xLimStartRatio, yDomain.lowerEndpoint() + yDomainSpan * xLimEndRatio)
val ratioX = spanX / displayW
val ratioY = spanY / displayH

val ratioX = span(xDomain) / displayW
// 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)

return Pair(xDomain, yDomain)
} else {
val spanAdjusted = displayW * ratioY
xDomain = SeriesUtil.expand(xDomain, spanAdjusted)
}

return Pair(xDomain, yDomain)
}

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

val ratioX = spanX / displayW
val ratioY = spanY / displayH
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)

// 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)
error("Impossible")
}

return Pair(xDomain, yDomain)
}
}
116 changes: 107 additions & 9 deletions plot-builder/src/jvmTest/kotlin/plot/builder/coord/CoordFixedTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@
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.assertEquals
import kotlin.test.fail

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

Expand All @@ -22,14 +25,96 @@ internal class CoordFixedTest : jetbrains.datalore.plot.builder.coord.CoordTestB

@Test
fun limits() {
val dataBounds = DoubleRectangle(0.0, 0.0, 5.0, 5.0)
val screenSize = DoubleVector(800.0, 600.0)
val (xDomain, yDomain) = CoordProviders
.fixed(1.0, ClosedRange.closed(1.0, 4.0))
.adjustDomains(dataBounds.xRange(), dataBounds.yRange(), screenSize)

assertEquals(ClosedRange.closed(800.0 * 0.2, 800.0 * 0.8), xDomain)
assertEquals(ClosedRange.closed(600.0 * 0.2, 600.0 * 0.8), yDomain)
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
Expand Down Expand Up @@ -120,6 +205,19 @@ internal class CoordFixedTest : jetbrains.datalore.plot.builder.coord.CoordTestB
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 {
private val PROVIDER_EQUAL_XY = CoordProviders.fixed(1.0)
private val PROVIDER_2x_Y = CoordProviders.fixed(2.0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ class CoordLim : PlotConfigDemoBase() {
fun plotSpecList(): List<Map<String, Any>> {
return listOf(
fixed(),
fixedLim()
//noLims(),
//xLims()
fixedXLim(7, 17),
fixedYLim(7, 17),
fixedLims(xMin = -1, xMax = 7, yMin = -1, yMax = 15)
)
}

Expand Down Expand Up @@ -49,35 +49,45 @@ class CoordLim : PlotConfigDemoBase() {
return parsePlotSpec(spec)
}

private fun noLims(): Map<String, Any> {
return createSpec(null).apply { this["ggtitle"] = mapOf("text" to "coord_cartesian(x_lim=[2, 22])") }
private fun fixedXLim(min: Number, max: Number): Map<String, Any> {
val spec = createSpec(
"""
| 'coord': {
| 'name': 'fixed',
| 'xlim': [$min, $max],
| 'ylim': null
| }
""".trimMargin()
)
spec["ggtitle"] = mapOf("text" to "coord_fixed(x_lim=[$min, $max])")
return spec
}

private fun xLims(): Map<String, Any> {
private fun fixedYLim(min: Number, max: Number): Map<String, Any> {
val spec = createSpec(
"""
| 'coord': {
| 'name': 'cartesian',
| 'xlim': [2, 22],
| 'ylim': null
| 'name': 'fixed',
| 'xlim': null,
| 'ylim': [$min, $max]
| }
""".trimMargin()
)
spec["ggtitle"] = mapOf("text" to "coord_cartesian(x_lim=[2, 22])")
spec["ggtitle"] = mapOf("text" to "coord_fixed(y_lim=[$min, $max])")
return spec
}

private fun fixedLim(): Map<String, Any> {
private fun fixedLims(xMin: Number, xMax: Number, yMin: Number, yMax: Number): Map<String, Any> {
val spec = createSpec(
"""
| 'coord': {
| 'name': 'fixed',
| 'xlim': [15, 30],
| 'ylim': null
| 'name': 'fixed',
| 'xlim': [$xMin, $xMax],
| 'ylim': [$yMin, $yMax]
| }
""".trimMargin()
)
spec["ggtitle"] = mapOf("text" to "coord_fixed(x_lim=[2, 22])")
spec["ggtitle"] = mapOf("text" to "coord_fixed(x_lim=[$xMin, $xMax], y_lim=[$yMin, $yMax])")
return spec
}

Expand Down