-
Notifications
You must be signed in to change notification settings - Fork 47
/
BoxHelper.kt
121 lines (102 loc) · 3.9 KB
/
BoxHelper.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
/*
* Copyright (c) 2023. JetBrains s.r.o.
* Use of this source code is governed by the MIT license that can be found in the LICENSE file.
*/
package org.jetbrains.letsPlot.core.plot.base.geom.util
import org.jetbrains.letsPlot.commons.geometry.DoubleRectangle
import org.jetbrains.letsPlot.commons.geometry.DoubleVector
import org.jetbrains.letsPlot.core.plot.base.*
import org.jetbrains.letsPlot.core.plot.base.aes.AesScaling
import org.jetbrains.letsPlot.core.plot.base.render.LegendKeyElementFactory
import org.jetbrains.letsPlot.core.plot.base.render.SvgRoot
import org.jetbrains.letsPlot.datamodel.svg.dom.SvgGElement
import org.jetbrains.letsPlot.datamodel.svg.dom.SvgLineElement
import org.jetbrains.letsPlot.datamodel.svg.dom.SvgRectElement
import org.jetbrains.letsPlot.datamodel.svg.dom.SvgShape
object BoxHelper {
fun buildBoxes(
root: SvgRoot,
aesthetics: Aesthetics,
pos: PositionAdjustment,
coord: CoordinateSystem,
ctx: GeomContext,
rectFactory: (DataPointAesthetics) -> DoubleRectangle?
) {
// rectangles
val helper = RectanglesHelper(aesthetics, pos, coord, ctx, rectFactory)
val rectangles = helper.createRectangles()
rectangles.forEach { root.add(it) }
}
fun buildMidlines(
root: SvgRoot,
aesthetics: Aesthetics,
xAes: Aes<Double>,
middleAes: Aes<Double>,
sizeAes: Aes<Double>,
ctx: GeomContext,
geomHelper: GeomHelper,
fatten: Double,
flip: Boolean = false
) {
val elementHelper = geomHelper.createSvgElementHelper()
for (p in aesthetics.dataPoints()) {
val x = p.finiteOrNull(xAes) ?: continue
val middle = p.finiteOrNull(middleAes) ?: continue
val w = p.finiteOrNull(sizeAes) ?: continue
val width = w * ctx.getResolution(xAes)
val (line) = elementHelper.createLine(
DoubleVector(x - width / 2, middle).flipIf(flip),
DoubleVector(x + width / 2, middle).flipIf(flip),
p
) { AesScaling.strokeWidth(it) * fatten } ?: continue
// TODO: use strokeScale in createLine() function
require(line is SvgShape)
root.add(line)
}
}
fun legendFactory(whiskers: Boolean): LegendKeyElementFactory =
BoxLegendKeyElementFactory(whiskers)
}
private class BoxLegendKeyElementFactory(val whiskers: Boolean) :
LegendKeyElementFactory {
override fun createKeyElement(p: DataPointAesthetics, size: DoubleVector): SvgGElement {
val whiskerSize = .2
val strokeWidth = AesScaling.strokeWidth(p)
val width = (size.x - strokeWidth) * .8 // a bit narrower
val height = size.y - strokeWidth
val x = (size.x - width) / 2
val y = strokeWidth / 2
// box
var boxHeight = height
var boxY = y
if (whiskers) {
boxHeight = height * (1 - 2 * whiskerSize)
boxY = y + height * whiskerSize
}
val rect = SvgRectElement(
x,
boxY,
width,
boxHeight
)
GeomHelper.decorate(rect, p)
// lines
val middleY = y + height * .5
val middle = SvgLineElement(x, middleY, x + width, middleY)
GeomHelper.decorate(middle, p)
val g = SvgGElement()
g.children().add(rect)
g.children().add(middle)
if (whiskers) {
val middleX = x + width * .5
val lowerWhisker =
SvgLineElement(middleX, y + height * (1 - whiskerSize), middleX, y + height)
GeomHelper.decorate(lowerWhisker, p)
val upperWhisker = SvgLineElement(middleX, y, middleX, y + height * whiskerSize)
GeomHelper.decorate(upperWhisker, p)
g.children().add(lowerWhisker)
g.children().add(upperWhisker)
}
return g
}
}