mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-13 01:08:25 +07:00
LineChart improvements (Highlight & DP) (#9210)
* LineChart improvements (Highlight & DP) * Fix civ icon at end of line not showing.
This commit is contained in:
@ -17,8 +17,9 @@ import kotlin.math.ceil
|
|||||||
import kotlin.math.log10
|
import kotlin.math.log10
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
|
import kotlin.math.sqrt
|
||||||
|
|
||||||
private data class DataPoint(val x: Int, val y: Int, val civ: Civilization)
|
private data class DataPoint<T>(val x: T, val y: T, val civ: Civilization)
|
||||||
|
|
||||||
class LineChart(
|
class LineChart(
|
||||||
data: Map<Int, Map<Civilization, Int>>,
|
data: Map<Int, Map<Civilization, Int>>,
|
||||||
@ -49,7 +50,7 @@ class LineChart(
|
|||||||
private val hasNegativeYValues: Boolean
|
private val hasNegativeYValues: Boolean
|
||||||
private val negativeYLabel: Int
|
private val negativeYLabel: Int
|
||||||
|
|
||||||
private val dataPoints: List<DataPoint> = data.flatMap { turn ->
|
private val dataPoints: List<DataPoint<Int>> = data.flatMap { turn ->
|
||||||
turn.value.map { (civ, value) ->
|
turn.value.map { (civ, value) ->
|
||||||
DataPoint(turn.key, value, civ)
|
DataPoint(turn.key, value, civ)
|
||||||
}
|
}
|
||||||
@ -189,20 +190,31 @@ class LineChart(
|
|||||||
}
|
}
|
||||||
for (civ in civIterationOrder) {
|
for (civ in civIterationOrder) {
|
||||||
val points = pointsByCiv[civ]!!
|
val points = pointsByCiv[civ]!!
|
||||||
for (i in 1 until points.size) {
|
val scaledPoints : List<DataPoint<Float>> = points.map {
|
||||||
val prevPoint = points[i - 1]
|
val yScale = if (it.y < 0f) negativeScaleY else scaleY
|
||||||
val currPoint = points[i]
|
DataPoint(linesMinX + it.x * scaleX, linesMinY + it.y * yScale, it.civ)
|
||||||
val prevPointYScale = if (prevPoint.y < 0f) negativeScaleY else scaleY
|
}
|
||||||
val currPointYScale = if (currPoint.y < 0f) negativeScaleY else scaleY
|
// Probably nobody can tell the difference of one pixel, so that seems like a reasonable epsilon.
|
||||||
|
val simplifiedScaledPoints = douglasPeucker(scaledPoints, 1f)
|
||||||
|
// Draw a background line for the selected civ. We need to do this before all other
|
||||||
|
// lines of the selected civ, but after all lines of other civs.
|
||||||
|
if (civ == selectedCiv) {
|
||||||
|
for (i in 1 until simplifiedScaledPoints.size) {
|
||||||
|
val a = simplifiedScaledPoints[i - 1]
|
||||||
|
val b = simplifiedScaledPoints[i]
|
||||||
drawLine(
|
drawLine(
|
||||||
batch,
|
batch, a.x, a.y, b.x, b.y,
|
||||||
linesMinX + prevPoint.x * scaleX, linesMinY + prevPoint.y * prevPointYScale,
|
civ.nation.getInnerColor(), chartLineWidth * 3
|
||||||
linesMinX + currPoint.x * scaleX, linesMinY + currPoint.y * currPointYScale,
|
|
||||||
civ.nation.getOuterColor(), chartLineWidth
|
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (i in 1 until simplifiedScaledPoints.size) {
|
||||||
|
val a = simplifiedScaledPoints[i - 1]
|
||||||
|
val b = simplifiedScaledPoints[i]
|
||||||
|
drawLine(batch, a.x, a.y, b.x, b.y, civ.nation.getOuterColor(), chartLineWidth)
|
||||||
|
|
||||||
// Draw the selected Civ icon on its last datapoint
|
// Draw the selected Civ icon on its last datapoint
|
||||||
if (i == points.size - 1 && selectedCiv == civ && selectedCiv in lastTurnDataPoints) {
|
if (i == simplifiedScaledPoints.size - 1 && selectedCiv == civ && selectedCiv in lastTurnDataPoints) {
|
||||||
val selectedCivIcon =
|
val selectedCivIcon =
|
||||||
VictoryScreenCivGroup(
|
VictoryScreenCivGroup(
|
||||||
selectedCiv,
|
selectedCiv,
|
||||||
@ -214,11 +226,7 @@ class LineChart(
|
|||||||
?: this
|
?: this
|
||||||
}
|
}
|
||||||
selectedCivIcon.run {
|
selectedCivIcon.run {
|
||||||
setPosition(
|
setPosition(b.x, b.y, Align.center)
|
||||||
linesMinX + currPoint.x * scaleX,
|
|
||||||
linesMinY + currPoint.y * currPointYScale,
|
|
||||||
Align.center
|
|
||||||
)
|
|
||||||
setSize(33f, 33f) // Dead Civs need this
|
setSize(33f, 33f) // Dead Civs need this
|
||||||
draw(batch, parentAlpha)
|
draw(batch, parentAlpha)
|
||||||
}
|
}
|
||||||
@ -230,8 +238,8 @@ class LineChart(
|
|||||||
batch.transformMatrix = oldTransformMatrix
|
batch.transformMatrix = oldTransformMatrix
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getLastTurnDataPoints(): MutableMap<Civilization, DataPoint> {
|
private fun getLastTurnDataPoints(): MutableMap<Civilization, DataPoint<Int>> {
|
||||||
val lastDataPoints = mutableMapOf<Civilization, DataPoint>()
|
val lastDataPoints = mutableMapOf<Civilization, DataPoint<Int>>()
|
||||||
for (dataPoint in dataPoints) {
|
for (dataPoint in dataPoints) {
|
||||||
if (!lastDataPoints.containsKey(dataPoint.civ) || lastDataPoints[dataPoint.civ]!!.x < dataPoint.x) {
|
if (!lastDataPoints.containsKey(dataPoint.civ) || lastDataPoints[dataPoint.civ]!!.x < dataPoint.x) {
|
||||||
lastDataPoints[dataPoint.civ] = dataPoint
|
lastDataPoints[dataPoint.civ] = dataPoint
|
||||||
@ -256,11 +264,82 @@ class LineChart(
|
|||||||
shapeRenderer.begin(ShapeRenderer.ShapeType.Filled)
|
shapeRenderer.begin(ShapeRenderer.ShapeType.Filled)
|
||||||
shapeRenderer.color = color
|
shapeRenderer.color = color
|
||||||
shapeRenderer.rectLine(x1, y1, x2, y2, width)
|
shapeRenderer.rectLine(x1, y1, x2, y2, width)
|
||||||
|
// Draw a circle at the beginning and end points of the line to make consecutive lines
|
||||||
|
// (which might point in different directions) connect nicely.
|
||||||
|
shapeRenderer.circle(x1, y1, width / 2)
|
||||||
|
shapeRenderer.circle(x2, y2, width / 2)
|
||||||
shapeRenderer.end()
|
shapeRenderer.end()
|
||||||
|
|
||||||
batch.begin()
|
batch.begin()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun douglasPeucker(points: List<DataPoint<Float>>, epsilon: Float): List<DataPoint<Float>> {
|
||||||
|
if (points.size < 3) {
|
||||||
|
return points
|
||||||
|
}
|
||||||
|
|
||||||
|
val dMax = FloatArray(points.size)
|
||||||
|
var index = 0
|
||||||
|
var maxDistance = 0.0f
|
||||||
|
|
||||||
|
// Find the point with the maximum distance from the line segment
|
||||||
|
for (i in 1 until points.lastIndex) {
|
||||||
|
val distance = perpendicularDistance(points[i], points[0], points.last())
|
||||||
|
dMax[i] = distance
|
||||||
|
|
||||||
|
if (distance > maxDistance) {
|
||||||
|
index = i
|
||||||
|
maxDistance = distance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the maximum distance is greater than epsilon, recursively simplify
|
||||||
|
val resultList: MutableList<DataPoint<Float>> = mutableListOf()
|
||||||
|
if (maxDistance > epsilon) {
|
||||||
|
val recursiveList1 = douglasPeucker(points.subList(0, index + 1), epsilon)
|
||||||
|
val recursiveList2 = douglasPeucker(points.subList(index, points.size), epsilon)
|
||||||
|
|
||||||
|
resultList.addAll(recursiveList1.subList(0, recursiveList1.lastIndex))
|
||||||
|
resultList.addAll(recursiveList2)
|
||||||
|
} else {
|
||||||
|
resultList.add(points.first())
|
||||||
|
resultList.add(points.last())
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultList
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculates the perpendicular distance between a point and a line segment
|
||||||
|
private fun perpendicularDistance(
|
||||||
|
point: DataPoint<Float>,
|
||||||
|
start: DataPoint<Float>,
|
||||||
|
end: DataPoint<Float>
|
||||||
|
): Float {
|
||||||
|
val x = point.x
|
||||||
|
val y = point.y
|
||||||
|
val x1 = start.x
|
||||||
|
val y1 = start.y
|
||||||
|
val x2 = end.x
|
||||||
|
val y2 = end.y
|
||||||
|
|
||||||
|
val a = x - x1
|
||||||
|
val b = y - y1
|
||||||
|
val c = x2 - x1
|
||||||
|
val d = y2 - y1
|
||||||
|
|
||||||
|
val dot = a * c + b * d
|
||||||
|
val lenSq = c * c + d * d
|
||||||
|
val param = if (lenSq == 0.0f) 0.0f else dot / lenSq
|
||||||
|
|
||||||
|
val xx = if (param < 0) x1 else if (param > 1) x2 else x1 + param * c
|
||||||
|
val yy = if (param < 0) y1 else if (param > 1) y2 else y1 + param * d
|
||||||
|
|
||||||
|
val dx = x - xx
|
||||||
|
val dy = y - yy
|
||||||
|
|
||||||
|
return sqrt((dx * dx + dy * dy).toDouble()).toFloat()
|
||||||
|
}
|
||||||
|
|
||||||
override fun getMinWidth() = chartWidth
|
override fun getMinWidth() = chartWidth
|
||||||
override fun getMinHeight() = chartHeight
|
override fun getMinHeight() = chartHeight
|
||||||
override fun getPrefWidth() = chartWidth
|
override fun getPrefWidth() = chartWidth
|
||||||
|
Reference in New Issue
Block a user