Skip to content

Commit

Permalink
Fix #403: Plot layout looks wrong
Browse files Browse the repository at this point in the history
  • Loading branch information
alshan authored and IKupriyanov-HORIS committed Jul 29, 2021
1 parent 19df839 commit a7c9510
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 123 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ internal class FacetGridPlotLayout(
val info = TileLayoutInfo(
bounds,
geomBounds,
TileLayoutBase.clipBounds(geomBounds),
XYPlotLayoutUtil.clipBounds(geomBounds),
tileInfo.layoutInfo.xAxisInfo,
tileInfo.layoutInfo.yAxisInfo,
xAxisShown = facetTile.xAxis,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ package jetbrains.datalore.plot.builder.layout

import jetbrains.datalore.base.geometry.DoubleRectangle
import jetbrains.datalore.base.geometry.DoubleVector
import jetbrains.datalore.plot.builder.layout.XYPlotLayoutUtil.GEOM_MIN_SIZE
import jetbrains.datalore.plot.builder.layout.XYPlotLayoutUtil.clipBounds
import jetbrains.datalore.plot.builder.layout.XYPlotLayoutUtil.geomBounds

internal class LiveMapTileLayout : TileLayoutBase() {
internal class LiveMapTileLayout : TileLayout {

override fun doLayout(preferredSize: DoubleVector): TileLayoutInfo {
var geomBounds = geomBounds(
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,68 @@ package jetbrains.datalore.plot.builder.layout

import jetbrains.datalore.base.geometry.DoubleRectangle
import jetbrains.datalore.base.geometry.DoubleVector
import jetbrains.datalore.plot.builder.guide.Orientation

internal object XYPlotLayoutUtil {
private const val GEOM_MARGIN = 10.0 // min space around geom area
const val GEOM_MARGIN = 0.0 // min space around geom area
private const val CLIP_EXTEND = 5.0
val GEOM_MIN_SIZE = DoubleVector(50.0, 50.0)

fun geomBounds(xAxisThickness: Double, yAxisThickness: Double, plotSize: DoubleVector): DoubleRectangle {
val marginLeftTop = DoubleVector(yAxisThickness, GEOM_MARGIN)
val marginRightBottom = DoubleVector(GEOM_MARGIN, xAxisThickness)
var geomSize = plotSize
.subtract(marginLeftTop)
.subtract(marginRightBottom)

if (geomSize.x < GEOM_MIN_SIZE.x) {
geomSize = DoubleVector(GEOM_MIN_SIZE.x, geomSize.y)
}
if (geomSize.y < GEOM_MIN_SIZE.y) {
geomSize = DoubleVector(geomSize.x, GEOM_MIN_SIZE.y)
}
return DoubleRectangle(marginLeftTop, geomSize)
}

fun clipBounds(geomBounds: DoubleRectangle): DoubleRectangle {
return DoubleRectangle(
geomBounds.origin.subtract(
DoubleVector(
CLIP_EXTEND,
CLIP_EXTEND
)
),
DoubleVector(
geomBounds.dimension.x + 2 * CLIP_EXTEND,
geomBounds.dimension.y + 2 * CLIP_EXTEND
)
)
}

fun maxTickLabelsBounds(
axisOrientation: Orientation,
stretch: Double,
geomBounds: DoubleRectangle,
plotSize: DoubleVector
): DoubleRectangle {
val geomPaddung = 10.0 // min space around geom area (labels should not touch geom area).

fun maxTickLabelsBounds(axisOrientation: jetbrains.datalore.plot.builder.guide.Orientation, stretch: Double, geomBounds: DoubleRectangle, plotSize: DoubleVector): DoubleRectangle {
val maxGeomBounds = DoubleRectangle(
GEOM_MARGIN,
GEOM_MARGIN, plotSize.x - 2 * GEOM_MARGIN, plotSize.y - 2 * GEOM_MARGIN
geomPaddung, geomPaddung,
plotSize.x - 2 * geomPaddung,
plotSize.y - 2 * geomPaddung
)
when (axisOrientation) {
jetbrains.datalore.plot.builder.guide.Orientation.TOP, jetbrains.datalore.plot.builder.guide.Orientation.BOTTOM -> {
Orientation.TOP,
Orientation.BOTTOM -> {
val leftSpace = geomBounds.left - maxGeomBounds.left + stretch
val rightSpace = maxGeomBounds.right - geomBounds.right + stretch

val height = Double.MAX_VALUE / 2 // just very large number
val top = if (axisOrientation === jetbrains.datalore.plot.builder.guide.Orientation.TOP)
-height
else
0.0
val height = 1E42 // just very large number
val top = when (axisOrientation) {
Orientation.TOP -> -height
else -> 0.0
}

val left = -leftSpace
val width = leftSpace + rightSpace + geomBounds.width
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,68 +8,30 @@ package jetbrains.datalore.plot.builder.layout
import jetbrains.datalore.base.geometry.DoubleRectangle
import jetbrains.datalore.base.geometry.DoubleVector
import jetbrains.datalore.plot.builder.guide.Orientation
import jetbrains.datalore.plot.builder.layout.XYPlotLayoutUtil.GEOM_MARGIN
import jetbrains.datalore.plot.builder.layout.XYPlotLayoutUtil.GEOM_MIN_SIZE
import jetbrains.datalore.plot.builder.layout.XYPlotLayoutUtil.clipBounds
import jetbrains.datalore.plot.builder.layout.XYPlotLayoutUtil.geomBounds
import jetbrains.datalore.plot.builder.layout.XYPlotLayoutUtil.maxTickLabelsBounds

internal class XYPlotTileLayout(
private val myXAxisLayout: AxisLayout,
private val myYAxisLayout: AxisLayout
) : TileLayoutBase() {
private val xAxisLayout: AxisLayout,
private val yAxisLayout: AxisLayout
) : TileLayout {

override fun doLayout(preferredSize: DoubleVector): TileLayoutInfo {
var xAxisThickness = myXAxisLayout.initialThickness()
var yAxisThickness = myYAxisLayout.initialThickness()

var geomBounds = geomBounds(
xAxisThickness,
yAxisThickness,
var (xAxisInfo, yAxisInfo) = computeAxisInfos(
xAxisLayout,
yAxisLayout,
preferredSize
)

var xAxisInfo: AxisLayoutInfo? = null
var yAxisInfo: AxisLayoutInfo? = null

var doX = true
while (doX) {
var doY = false
doX = false
run {
val axisLength = geomBounds.dimension.x
val stretch = axisLength * AXIS_STRETCH_RATIO
val maxTickLabelsBounds = maxTickLabelsBounds(
Orientation.BOTTOM,
stretch,
geomBounds,
preferredSize
)
xAxisInfo = myXAxisLayout.doLayout(geomBounds.dimension, maxTickLabelsBounds)
val axisThicknessNew = xAxisInfo!!.axisBounds().dimension.y
if (axisThicknessNew > xAxisThickness) {
doY = true // do Y if X got tallier
geomBounds =
geomBounds(
axisThicknessNew,
yAxisThickness,
preferredSize
)
}
xAxisThickness = axisThicknessNew
}

if (doY || yAxisInfo == null) {
yAxisInfo = myYAxisLayout.doLayout(geomBounds.dimension, null)
val axisThicknessNew = yAxisInfo.axisBounds().dimension.x
if (axisThicknessNew > yAxisThickness) {
doX = true // do X again if Y got wider
geomBounds =
geomBounds(
xAxisThickness,
axisThicknessNew,
preferredSize
)
}
yAxisThickness = axisThicknessNew
}
}
var geomBounds = geomBounds(
xAxisThickness = xAxisInfo.axisBounds().dimension.y,
yAxisThickness = yAxisInfo.axisBounds().dimension.x,
preferredSize
)

// X-axis labels bounds may exceed axis length - adjust
run {
Expand All @@ -79,7 +41,7 @@ internal class XYPlotTileLayout(
geomBounds,
preferredSize
)
val tickLabelsBounds = xAxisInfo!!.tickLabelsBounds
val tickLabelsBounds = xAxisInfo.tickLabelsBounds
val leftOverflow = maxTickLabelsBounds.left - tickLabelsBounds!!.origin.x
val rightOverflow = tickLabelsBounds.origin.x + tickLabelsBounds.dimension.x - maxTickLabelsBounds.right
if (leftOverflow > 0) {
Expand Down Expand Up @@ -107,20 +69,20 @@ internal class XYPlotTileLayout(
// Combine geom area and x/y axis
val geomWithAxisBounds =
tileBounds(
xAxisInfo!!.axisBounds(),
yAxisInfo!!.axisBounds(),
xAxisInfo.axisBounds(),
yAxisInfo.axisBounds(),
geomBounds
)

// sync axis info with new (may be) geom area size
xAxisInfo = xAxisInfo!!.withAxisLength(geomBounds.width).build()
xAxisInfo = xAxisInfo.withAxisLength(geomBounds.width).build()
yAxisInfo = yAxisInfo.withAxisLength(geomBounds.height).build()

return TileLayoutInfo(
geomWithAxisBounds,
geomBounds,
clipBounds(geomBounds),
xAxisInfo!!,
xAxisInfo,
yAxisInfo,
trueIndex = 0
)
Expand All @@ -147,5 +109,68 @@ internal class XYPlotTileLayout(
)
return DoubleRectangle(leftTop, rightBottom.subtract(leftTop))
}

private fun computeAxisInfos(
xAxisLayout: AxisLayout,
yAxisLayout: AxisLayout,
plotSize: DoubleVector
): Pair<AxisLayoutInfo, AxisLayoutInfo> {
val xAxisThickness = xAxisLayout.initialThickness()
var yAxisInfo = computeYAxisInfo(
yAxisLayout,
geomBounds(
xAxisThickness,
yAxisLayout.initialThickness(),
plotSize
)
)

val yAxisThickness = yAxisInfo.axisBounds().dimension.x
var xAxisInfo = computeXAxisInfo(
xAxisLayout,
plotSize, geomBounds(
xAxisThickness,
yAxisThickness,
plotSize
)
)

if (xAxisInfo.axisBounds().dimension.y > xAxisThickness) {
// Re-layout y-axis if x-axis became thicker.
yAxisInfo = computeYAxisInfo(
yAxisLayout,
geomBounds(
xAxisInfo.axisBounds().dimension.y,
yAxisThickness,
plotSize
)
)
}

return Pair(xAxisInfo, yAxisInfo)
}

private fun computeXAxisInfo(
axisLayout: AxisLayout,
plotSize: DoubleVector,
geomBounds: DoubleRectangle
): AxisLayoutInfo {
val axisLength = geomBounds.dimension.x
val stretch = axisLength * AXIS_STRETCH_RATIO
val maxTickLabelsBounds = maxTickLabelsBounds(
Orientation.BOTTOM,
stretch,
geomBounds,
plotSize
)
return axisLayout.doLayout(geomBounds.dimension, maxTickLabelsBounds)
}

private fun computeYAxisInfo(
axisLayout: AxisLayout,
geomBounds: DoubleRectangle
): AxisLayoutInfo {
return axisLayout.doLayout(geomBounds.dimension, null)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,42 @@

package jetbrains.datalore.plot.builder.layout.axis.label

import jetbrains.datalore.base.gcommon.base.Preconditions.checkArgument
import jetbrains.datalore.base.gcommon.collect.ClosedRange
import jetbrains.datalore.base.geometry.DoubleRectangle
import jetbrains.datalore.base.geometry.DoubleVector
import jetbrains.datalore.plot.builder.guide.Orientation
import jetbrains.datalore.plot.builder.layout.axis.GuideBreaks
import jetbrains.datalore.plot.builder.presentation.PlotLabelSpec
import jetbrains.datalore.plot.builder.theme.AxisTheme

internal class HorizontalFixedBreaksLabelsLayout(
orientation: jetbrains.datalore.plot.builder.guide.Orientation,
orientation: Orientation,
axisDomain: ClosedRange<Double>,
labelSpec: PlotLabelSpec,
breaks: GuideBreaks, theme: AxisTheme) :
AbstractFixedBreaksLabelsLayout(orientation, axisDomain, labelSpec, breaks, theme) {

breaks: GuideBreaks,
theme: AxisTheme
) : AbstractFixedBreaksLabelsLayout(
orientation,
axisDomain,
labelSpec,
breaks,
theme
) {
init {
checkArgument(orientation.isHorizontal, orientation.toString())
require(orientation.isHorizontal) { orientation.toString() }
}

private fun overlap(labelsInfo: AxisLabelsLayoutInfo, maxTickLabelsBounds: DoubleRectangle?): Boolean {
return labelsInfo.isOverlap || maxTickLabelsBounds != null && !(maxTickLabelsBounds.xRange().encloses(labelsInfo.bounds!!.xRange()) && maxTickLabelsBounds.yRange().encloses(labelsInfo.bounds.yRange()))
return labelsInfo.isOverlap || maxTickLabelsBounds != null && !(maxTickLabelsBounds.xRange()
.encloses(labelsInfo.bounds!!.xRange()) && maxTickLabelsBounds.yRange()
.encloses(labelsInfo.bounds.yRange()))
}

override fun doLayout(axisLength: Double, axisMapper: (Double?) -> Double?, maxLabelsBounds: DoubleRectangle?): AxisLabelsLayoutInfo {
override fun doLayout(
axisLength: Double,
axisMapper: (Double?) -> Double?,
maxLabelsBounds: DoubleRectangle?
): AxisLabelsLayoutInfo {
if (!theme.showTickLabels()) {
return noLabelsLayoutInfo(axisLength, orientation)
}
Expand All @@ -41,7 +53,8 @@ internal class HorizontalFixedBreaksLabelsLayout(
if (overlap(labelsInfo, maxLabelsBounds)) {
labelsInfo = verticalLayout(labelSpec).doLayout(axisLength, axisMapper, maxLabelsBounds)
if (overlap(labelsInfo, maxLabelsBounds)) {
labelsInfo = verticalLayout(TICK_LABEL_SPEC_SMALL).doLayout(axisLength, axisMapper, maxLabelsBounds)
labelsInfo =
verticalLayout(TICK_LABEL_SPEC_SMALL).doLayout(axisLength, axisMapper, maxLabelsBounds)
}
}
}
Expand Down

0 comments on commit a7c9510

Please sign in to comment.