commonMain.jetbrains.datalore.plot.builder.SquareFrameOfReference.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of lets-plot-common Show documentation
Show all versions of lets-plot-common Show documentation
Lets-Plot JVM package without rendering part
/*
* Copyright (c) 2021. JetBrains s.r.o.
* Use of this source code is governed by the MIT license that can be found in the LICENSE file.
*/
package jetbrains.datalore.plot.builder
import jetbrains.datalore.base.gcommon.collect.ClosedRange
import jetbrains.datalore.base.geometry.DoubleRectangle
import jetbrains.datalore.base.geometry.DoubleVector
import jetbrains.datalore.base.values.Color
import jetbrains.datalore.plot.base.CoordinateSystem
import jetbrains.datalore.plot.base.Scale
import jetbrains.datalore.plot.base.interact.GeomTargetCollector
import jetbrains.datalore.plot.base.render.svg.SvgComponent
import jetbrains.datalore.plot.builder.assemble.GeomContextBuilder
import jetbrains.datalore.plot.builder.guide.AxisComponent
import jetbrains.datalore.plot.builder.layout.AxisLayoutInfo
import jetbrains.datalore.plot.builder.layout.TileLayoutInfo
import jetbrains.datalore.plot.builder.theme.AxisTheme
import jetbrains.datalore.plot.builder.theme.PanelGridTheme
import jetbrains.datalore.plot.builder.theme.PanelTheme
import jetbrains.datalore.plot.builder.theme.Theme
import jetbrains.datalore.vis.svg.SvgRectElement
internal class SquareFrameOfReference(
private val hScale: Scale,
private val vScale: Scale,
private val coord: CoordinateSystem,
private val layoutInfo: TileLayoutInfo,
private val theme: Theme,
private val flipAxis: Boolean,
) : TileFrameOfReference {
var isDebugDrawing: Boolean = false
private val geomMapperX: (Double?) -> Double?
private val geomMapperY: (Double?) -> Double?
private val geomCoord: CoordinateSystem
init {
if (flipAxis) {
// flip mappers to 'fool' geom.
geomMapperX = vScale.mapper
geomMapperY = hScale.mapper
geomCoord = coord.flip()
} else {
geomMapperX = hScale.mapper
geomMapperY = vScale.mapper
geomCoord = coord
}
}
// Rendering
override fun drawFoR(parent: SvgComponent) {
val geomBounds: DoubleRectangle = layoutInfo.geomBounds
val panelTheme = theme.panel()
// Flip theme
val hAxisTheme = theme.axisX(flipAxis)
val vAxisTheme = theme.axisY(flipAxis)
val hGridTheme = panelTheme.gridX(flipAxis)
val vGridTheme = panelTheme.gridY(flipAxis)
if (panelTheme.showRect()) {
val panel = buildPanelComponent(geomBounds, panelTheme)
parent.add(panel)
}
// X-axis (below geom area)
val hAxis = buildAxis(
hScale,
layoutInfo.xAxisInfo!!,
hideAxisBreaks = !layoutInfo.xAxisShown,
coord,
hAxisTheme,
hGridTheme,
geomBounds.height,
isDebugDrawing
)
hAxis.moveTo(DoubleVector(geomBounds.left, geomBounds.bottom))
parent.add(hAxis)
// Y-axis (to the left from geom area, axis elements have negative x-positions)
val vAxis = buildAxis(
vScale,
layoutInfo.yAxisInfo!!,
hideAxisBreaks = !layoutInfo.yAxisShown,
coord,
vAxisTheme,
vGridTheme,
geomBounds.width,
isDebugDrawing
)
vAxis.moveTo(geomBounds.origin)
parent.add(vAxis)
if (isDebugDrawing) {
drawDebugShapes(parent, geomBounds)
}
}
private fun drawDebugShapes(parent: SvgComponent, geomBounds: DoubleRectangle) {
run {
val tileBounds = layoutInfo.bounds
val rect = SvgRectElement(tileBounds)
rect.fillColor().set(Color.BLACK)
rect.strokeWidth().set(0.0)
rect.fillOpacity().set(0.1)
parent.add(rect)
}
run {
val clipBounds = layoutInfo.clipBounds
val rect = SvgRectElement(clipBounds)
rect.fillColor().set(Color.DARK_GREEN)
rect.strokeWidth().set(0.0)
rect.fillOpacity().set(0.3)
parent.add(rect)
}
run {
val rect = SvgRectElement(geomBounds)
rect.fillColor().set(Color.PINK)
rect.strokeWidth().set(1.0)
rect.fillOpacity().set(0.5)
parent.add(rect)
}
}
override fun buildGeomComponent(layer: GeomLayer, targetCollector: GeomTargetCollector): SvgComponent {
val hAxisMapper = hScale.mapper
val vAxisMapper = vScale.mapper
val hAxisDomain = layoutInfo.xAxisInfo!!.axisDomain!!
val vAxisDomain = layoutInfo.yAxisInfo!!.axisDomain!!
val aesBounds = DoubleRectangle(
xRange = ClosedRange(
hAxisMapper(hAxisDomain.lowerEnd) as Double,
hAxisMapper(hAxisDomain.upperEnd) as Double
),
yRange = ClosedRange(
vAxisMapper(vAxisDomain.lowerEnd) as Double,
vAxisMapper(vAxisDomain.upperEnd) as Double
)
)
return buildGeom(
layer,
geomMapperX, geomMapperY,
xyAesBounds = aesBounds,
geomCoord,
flipAxis,
targetCollector
)
}
override fun applyClientLimits(clientBounds: DoubleRectangle): DoubleRectangle {
return geomCoord.applyClientLimits(clientBounds)
}
companion object {
private fun buildAxis(
scale: Scale,
info: AxisLayoutInfo,
hideAxisBreaks: Boolean,
coord: CoordinateSystem,
axisTheme: AxisTheme,
gridTheme: PanelGridTheme,
gridLineLength: Double,
isDebugDrawing: Boolean
): AxisComponent {
// val axis = AxisComponent(info.axisLength, info.orientation!!)
// if (gridTheme.showMajor()) {
// axis.gridLineLength.set(gridLineLength)
// axis.gridLineWidth.set(gridTheme.majorLineWidth())
// axis.gridLineColor.set(gridTheme.majorLineColor())
// }
// AxisUtil.setBreaks(axis, scale, coord, info.orientation.isHorizontal)
// AxisUtil.applyLayoutInfo(axis, info)
// AxisUtil.applyTheme(axis, axisTheme, hideAxisBreaks)
val orientation = info.orientation!!
val labelAdjustments = AxisComponent.TickLabelAdjustments(
orientation = orientation,
horizontalAnchor = info.tickLabelHorizontalAnchor,
verticalAnchor = info.tickLabelVerticalAnchor,
rotationDegree = info.tickLabelRotationAngle,
additionalOffsets = info.tickLabelAdditionalOffsets
)
val axis = AxisComponent(
length = info.axisLength,
orientation = orientation,
breaksData = AxisUtil.breaksData(scale, coord, orientation.isHorizontal),
labelAdjustments = labelAdjustments,
gridLineLength = gridLineLength,
axisTheme = axisTheme,
gridTheme = gridTheme,
useSmallFont = info.tickLabelSmallFont,
hideAxisBreaks = hideAxisBreaks
)
if (isDebugDrawing) {
if (info.tickLabelsBounds != null) {
val rect = SvgRectElement(info.tickLabelsBounds)
rect.strokeColor().set(Color.GREEN)
rect.strokeWidth().set(1.0)
rect.fillOpacity().set(0.0)
axis.add(rect)
}
}
return axis
}
private fun buildPanelComponent(bounds: DoubleRectangle, theme: PanelTheme): SvgRectElement {
return SvgRectElement(bounds).apply {
strokeColor().set(theme.rectColor())
strokeWidth().set(theme.rectStrokeWidth())
fillColor().set(theme.rectFill())
}
}
private fun buildGeom(
layer: GeomLayer,
xAesMapper: (Double?) -> Double?,
yAesMapper: (Double?) -> Double?,
xyAesBounds: DoubleRectangle,
coord: CoordinateSystem,
flippedAxis: Boolean,
targetCollector: GeomTargetCollector
): SvgComponent {
val rendererData = LayerRendererUtil.createLayerRendererData(
layer,
xAesMapper, yAesMapper
)
val aestheticMappers = rendererData.aestheticMappers
val aesthetics = rendererData.aesthetics
val ctx = GeomContextBuilder()
.flipped(flippedAxis)
.aesthetics(aesthetics)
.aestheticMappers(aestheticMappers)
.aesBounds(xyAesBounds)
.geomTargetCollector(
if (flippedAxis) {
targetCollector.flip()
} else {
targetCollector
}
)
.build()
val pos = rendererData.pos
val geom = layer.geom
return SvgLayerRenderer(aesthetics, geom, pos, coord, ctx)
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy