mirror of
https://github.com/yairm210/Unciv.git
synced 2025-02-13 12:27:40 +07:00
Zoom in/out of the history charts (#9660)
* Do not recreate the Line Chart every time * Simplifed the Line Chart creation * Do not create objects in draw(): VictoryScreenCivGroup table * Do not create objects in draw(): Labels * Create labels without negative Y * Lift the X axis if there is an negative number * Arbitrary number of -Y labels * Autoscale by Y axis * Zoom in/out by click * Autoscale by X axis * Unit tests for LineChart * Rework of the line chart rendering
This commit is contained in:
parent
1285133884
commit
82ebb01a20
@ -2,35 +2,28 @@ package com.unciv.ui.components
|
|||||||
|
|
||||||
import com.badlogic.gdx.graphics.Color
|
import com.badlogic.gdx.graphics.Color
|
||||||
import com.badlogic.gdx.graphics.g2d.Batch
|
import com.badlogic.gdx.graphics.g2d.Batch
|
||||||
import com.badlogic.gdx.graphics.glutils.ShapeRenderer
|
import com.badlogic.gdx.math.MathUtils.lerp
|
||||||
import com.badlogic.gdx.math.Matrix4
|
|
||||||
import com.badlogic.gdx.math.Vector2
|
|
||||||
import com.badlogic.gdx.scenes.scene2d.ui.Image
|
|
||||||
import com.badlogic.gdx.scenes.scene2d.ui.Label
|
import com.badlogic.gdx.scenes.scene2d.ui.Label
|
||||||
import com.badlogic.gdx.scenes.scene2d.ui.Widget
|
import com.badlogic.gdx.scenes.scene2d.ui.WidgetGroup
|
||||||
import com.badlogic.gdx.utils.Align
|
import com.badlogic.gdx.utils.Align
|
||||||
import com.unciv.logic.civilization.Civilization
|
import com.unciv.logic.civilization.Civilization
|
||||||
import com.unciv.ui.components.extensions.surroundWithCircle
|
import com.unciv.ui.images.ImageGetter
|
||||||
import com.unciv.ui.screens.victoryscreen.VictoryScreenCivGroup
|
import com.unciv.ui.screens.victoryscreen.VictoryScreenCivGroup
|
||||||
import com.unciv.ui.screens.victoryscreen.VictoryScreenCivGroup.DefeatedPlayerStyle
|
import com.unciv.ui.screens.victoryscreen.VictoryScreenCivGroup.DefeatedPlayerStyle
|
||||||
|
import kotlin.math.abs
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
import kotlin.math.ceil
|
import kotlin.math.ceil
|
||||||
|
import kotlin.math.floor
|
||||||
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
|
import kotlin.math.sqrt
|
||||||
|
|
||||||
private data class DataPoint<T>(val x: T, val y: T, val civ: Civilization)
|
data class DataPoint<T>(val x: T, val y: T, val civ: Civilization)
|
||||||
|
|
||||||
class LineChart(
|
class LineChart(
|
||||||
data: Map<Int, Map<Civilization, Int>>,
|
private val viewingCiv: Civilization
|
||||||
private val viewingCiv: Civilization,
|
) : WidgetGroup() {
|
||||||
private val selectedCiv: Civilization,
|
|
||||||
private val chartWidth: Float,
|
|
||||||
private val chartHeight: Float
|
|
||||||
) : Widget() {
|
|
||||||
|
|
||||||
private val shapeRenderer = ShapeRenderer()
|
|
||||||
|
|
||||||
private val axisLineWidth = 2f
|
private val axisLineWidth = 2f
|
||||||
private val axisColor = Color.WHITE
|
private val axisColor = Color.WHITE
|
||||||
@ -45,40 +38,85 @@ class LineChart(
|
|||||||
* as `0` is not counted. */
|
* as `0` is not counted. */
|
||||||
private val maxLabels = 10
|
private val maxLabels = 10
|
||||||
|
|
||||||
private val xLabels: List<Int>
|
private var xLabels = emptyList<Int>()
|
||||||
private val yLabels: List<Int>
|
private var yLabels = emptyList<Int>()
|
||||||
|
private var xLabelsAsLabels = emptyList<Label>()
|
||||||
|
private var yLabelsAsLabels = emptyList<Label>()
|
||||||
|
|
||||||
private val hasNegativeYValues: Boolean
|
private var dataPoints = emptyList<DataPoint<Int>>()
|
||||||
private val negativeYLabel: Int
|
private var selectedCiv = Civilization()
|
||||||
|
|
||||||
private val dataPoints: List<DataPoint<Int>> = data.flatMap { turn ->
|
|
||||||
turn.value.map { (civ, value) ->
|
|
||||||
DataPoint(turn.key, value, civ)
|
fun getTurnAt(x: Float): IntRange? {
|
||||||
|
if (xLabels.isEmpty() || xLabelsAsLabels.isEmpty() || yLabelsAsLabels.isEmpty()) return null
|
||||||
|
val widestYLabelWidth = yLabelsAsLabels.maxOf { it.width }
|
||||||
|
val linesMinX = widestYLabelWidth + axisToLabelPadding + axisLineWidth
|
||||||
|
val linesMaxX = width - xLabelsAsLabels.last().width / 2
|
||||||
|
if (linesMinX.compareTo(linesMaxX) == 0) return (xLabels.first()..xLabels.last())
|
||||||
|
val ratio = (x - linesMinX) / (linesMaxX - linesMinX)
|
||||||
|
val turn = max(1, lerp(xLabels.first().toFloat(), xLabels.last().toFloat(), ratio).toInt())
|
||||||
|
return (getPrevNumberDivisibleByPowOfTen(turn-1)..getNextNumberDivisibleByPowOfTen(turn+1))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun update(newData: List<DataPoint<Int>>, newSelectedCiv: Civilization) {
|
||||||
|
selectedCiv = newSelectedCiv
|
||||||
|
|
||||||
|
dataPoints = newData
|
||||||
|
updateLabels(dataPoints)
|
||||||
|
prepareForDraw()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateLabels(newData: List<DataPoint<Int>>) {
|
||||||
|
xLabels = generateLabels(newData, false)
|
||||||
|
yLabels = generateLabels(newData, true)
|
||||||
|
|
||||||
|
xLabelsAsLabels =
|
||||||
|
xLabels.map { Label(it.toString(), Label.LabelStyle(Fonts.font, axisLabelColor)) }
|
||||||
|
yLabelsAsLabels =
|
||||||
|
yLabels.map { Label(it.toString(), Label.LabelStyle(Fonts.font, axisLabelColor)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun generateLabels(value: List<DataPoint<Int>>, yAxis: Boolean): List<Int> {
|
||||||
|
if (value.isEmpty()) return listOf(0)
|
||||||
|
val minLabelValue = getPrevNumberDivisibleByPowOfTen(value.minOf { if (yAxis) it.y else it.x })
|
||||||
|
val maxLabelValue = getNextNumberDivisibleByPowOfTen(value.maxOf { if (yAxis) it.y else it.x })
|
||||||
|
var stepSizePositive = ceil(maxLabelValue.toFloat() / maxLabels).toInt()
|
||||||
|
|
||||||
|
return when {
|
||||||
|
minLabelValue < 0 -> {
|
||||||
|
var stepSizeNegative = ceil(-minLabelValue.toFloat() / maxLabels).toInt()
|
||||||
|
val maxStep = max(stepSizePositive, stepSizeNegative)
|
||||||
|
val stepCountNegative = floor(minLabelValue / maxStep.toDouble()).toInt()
|
||||||
|
stepSizeNegative = if (abs(stepCountNegative) < 2) abs(minLabelValue) else maxStep
|
||||||
|
val stepCountPositive = ceil(maxLabelValue / maxStep.toDouble()).toInt()
|
||||||
|
stepSizePositive = if (abs(stepCountPositive) < 2) abs(maxLabelValue) else maxStep
|
||||||
|
|
||||||
|
(stepCountNegative until 0).map { (it * stepSizeNegative) } +
|
||||||
|
if (maxLabelValue != 0)
|
||||||
|
(0 until stepCountPositive + 1).map { (it * stepSizePositive) }
|
||||||
|
else listOf(0)
|
||||||
|
}
|
||||||
|
maxLabelValue != 0 -> {
|
||||||
|
// `maxLabels + 1` because we want to end at `maxLabels * stepSize`.
|
||||||
|
if (minLabelValue < stepSizePositive)
|
||||||
|
(0 until maxLabels + 1).map { (it * stepSizePositive) }
|
||||||
|
else {
|
||||||
|
stepSizePositive = ceil((maxLabelValue-minLabelValue).toFloat() / maxLabels).toInt()
|
||||||
|
(0 until maxLabels + 1).map { minLabelValue + (it * stepSizePositive) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> listOf(0, 1) // line along 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
|
||||||
hasNegativeYValues = dataPoints.any { it.y < 0 }
|
|
||||||
xLabels = generateLabels(dataPoints.maxOf { it.x })
|
|
||||||
yLabels = generateLabels(dataPoints.maxOf { it.y })
|
|
||||||
val lowestValue = dataPoints.minOf { it.y }
|
|
||||||
negativeYLabel = if (hasNegativeYValues) -getNextNumberDivisibleByPowOfTen(-lowestValue) else 0
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun generateLabels(maxValue: Int): List<Int> {
|
|
||||||
val maxLabelValue = getNextNumberDivisibleByPowOfTen(maxValue)
|
|
||||||
val stepSize = ceil(maxLabelValue.toFloat() / maxLabels).toInt()
|
|
||||||
// `maxLabels + 1` because we want to end at `maxLabels * stepSize`.
|
|
||||||
return (0 until maxLabels + 1).map { (it * stepSize) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the next number of power 10, with maximal step <= 100.
|
* Returns the next number of power 10, with maximal step <= 100.
|
||||||
* Examples: 0 => 0, 3 => 10, 97 => 100, 567 => 600, 123321 => 123400
|
* Examples: 0 => 0, 3 => 10, 97 => 100, 567 => 600, 123321 => 123400
|
||||||
*/
|
*/
|
||||||
private fun getNextNumberDivisibleByPowOfTen(value: Int): Int {
|
private fun getNextNumberDivisibleByPowOfTen(value: Int): Int {
|
||||||
if (value == 0) return 0
|
if (value == 0) return 0
|
||||||
val numberOfDigits = min(ceil(log10(value.toDouble())).toInt(), 3)
|
val numberOfDigits = max(2, min(ceil(log10(abs(value).toDouble())).toInt(), 3))
|
||||||
val oneWithZeros = 10.0.pow(numberOfDigits - 1)
|
val oneWithZeros = 10.0.pow(numberOfDigits - 1)
|
||||||
// E.g., 3 => 10^(2-1) = 10 ; ceil(3 / 10) * 10 = 10
|
// E.g., 3 => 10^(2-1) = 10 ; ceil(3 / 10) * 10 = 10
|
||||||
// 567 => 10^(3-1) = 100 ; ceil(567 / 100) * 100 = 600
|
// 567 => 10^(3-1) = 100 ; ceil(567 / 100) * 100 = 600
|
||||||
@ -86,28 +124,39 @@ class LineChart(
|
|||||||
return (ceil(value / oneWithZeros) * oneWithZeros).toInt()
|
return (ceil(value / oneWithZeros) * oneWithZeros).toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the previous number of power 10, with maximal step <= 100.
|
||||||
|
* Examples: 0 => 0, -3 => -10, 97 => 90, 567 => 500, 123321 => 123300
|
||||||
|
*/
|
||||||
|
private fun getPrevNumberDivisibleByPowOfTen(value: Int): Int {
|
||||||
|
if (value == 0) return 0
|
||||||
|
val numberOfDigits = max(2 , min(ceil(log10(abs(value).toDouble())).toInt(), 3))
|
||||||
|
val oneWithZeros = 10.0.pow(numberOfDigits - 1)
|
||||||
|
// E.g., 3 => 10^(2-1) = 10 ; floor(3 / 10) * 10 = 0
|
||||||
|
// 567 => 10^(3-1) = 100 ; floor(567 / 100) * 100 = 500
|
||||||
|
// 123321 => 10^(3-1) = 100 ; floor(123321 / 100) * 100 = 123300
|
||||||
|
return (floor(value / oneWithZeros) * oneWithZeros).toInt()
|
||||||
|
}
|
||||||
|
|
||||||
override fun draw(batch: Batch, parentAlpha: Float) {
|
override fun draw(batch: Batch, parentAlpha: Float) {
|
||||||
super.draw(batch, parentAlpha)
|
super.draw(batch, parentAlpha)
|
||||||
|
}
|
||||||
|
|
||||||
// Save the current batch transformation matrix
|
private fun prepareForDraw() {
|
||||||
val oldTransformMatrix = batch.transformMatrix.cpy()
|
|
||||||
// Set the batch transformation matrix to the local coordinates of the LineChart widget
|
clearChildren()
|
||||||
val stageCoords = localToStageCoordinates(Vector2(0f, 0f))
|
|
||||||
batch.transformMatrix = Matrix4().translate(stageCoords.x, stageCoords.y, 0f)
|
if (xLabels.isEmpty() || yLabels.isEmpty()) return
|
||||||
|
|
||||||
val lastTurnDataPoints = getLastTurnDataPoints()
|
val lastTurnDataPoints = getLastTurnDataPoints()
|
||||||
|
|
||||||
val labelHeight = Label("123", Label.LabelStyle(Fonts.font, axisLabelColor)).height
|
val labelHeight = yLabelsAsLabels.first().height
|
||||||
val yLabelsAsLabels =
|
val widestYLabelWidth = yLabelsAsLabels.maxOf { it.width }
|
||||||
yLabels.map { Label(it.toString(), Label.LabelStyle(Fonts.font, axisLabelColor)) }
|
|
||||||
val negativeYLabelAsLabel =
|
|
||||||
Label(negativeYLabel.toString(), Label.LabelStyle(Fonts.font, axisLabelColor))
|
|
||||||
val widestYLabelWidth = max(yLabelsAsLabels.maxOf { it.width }, negativeYLabelAsLabel.width)
|
|
||||||
// We assume here that all labels have the same height. We need to deduct the height of
|
// We assume here that all labels have the same height. We need to deduct the height of
|
||||||
// a label from the available height, because otherwise the label on the top would
|
// a label from the available height, because otherwise the label on the top would
|
||||||
// overrun the height since the (x,y) coordinates define the bottom left corner of the
|
// overrun the height since the (x,y) coordinates define the bottom left corner of the
|
||||||
// Label.
|
// Label.
|
||||||
val yAxisLabelMaxY = chartHeight - labelHeight
|
val yAxisLabelMaxY = height - labelHeight
|
||||||
// This is to account for the x axis and its labels which are below the lowest point
|
// This is to account for the x axis and its labels which are below the lowest point
|
||||||
val xAxisLabelsHeight = labelHeight
|
val xAxisLabelsHeight = labelHeight
|
||||||
val zeroYAxisLabelHeight = labelHeight
|
val zeroYAxisLabelHeight = labelHeight
|
||||||
@ -118,63 +167,60 @@ class LineChart(
|
|||||||
// We draw the y-axis labels first. They will take away some space on the left of the
|
// We draw the y-axis labels first. They will take away some space on the left of the
|
||||||
// widget which we need to consider when drawing the rest of the graph.
|
// widget which we need to consider when drawing the rest of the graph.
|
||||||
var yAxisYPosition = 0f
|
var yAxisYPosition = 0f
|
||||||
val negativeOrientationLineYPosition = yAxisLabelMinY + labelHeight / 2
|
yLabels.forEachIndexed { index, value ->
|
||||||
val yLabelsToDraw = if (hasNegativeYValues) listOf(negativeYLabelAsLabel) + yLabelsAsLabels else yLabelsAsLabels
|
val label = yLabelsAsLabels[index] // we assume yLabels.size == yLabelsAsLabels.size
|
||||||
yLabelsToDraw.forEachIndexed { index, label ->
|
val yPos = yAxisLabelMinY + index * (yAxisLabelYRange / (yLabels.size - 1))
|
||||||
val yPos = yAxisLabelMinY + index * (yAxisLabelYRange / (yLabelsToDraw.size - 1))
|
|
||||||
label.setPosition((widestYLabelWidth - label.width) / 2, yPos)
|
label.setPosition((widestYLabelWidth - label.width) / 2, yPos)
|
||||||
label.draw(batch, 1f)
|
addActor(label)
|
||||||
|
|
||||||
// Draw y-axis orientation lines and x-axis
|
// Draw y-axis orientation lines and x-axis
|
||||||
val zeroIndex = if (hasNegativeYValues) 1 else 0
|
val zeroIndex = value == 0
|
||||||
drawLine(
|
drawLine(
|
||||||
batch,
|
|
||||||
widestYLabelWidth + axisToLabelPadding + axisLineWidth,
|
widestYLabelWidth + axisToLabelPadding + axisLineWidth,
|
||||||
yPos + labelHeight / 2,
|
yPos + labelHeight / 2,
|
||||||
chartWidth,
|
width,
|
||||||
yPos + labelHeight / 2,
|
yPos + labelHeight / 2,
|
||||||
if (index != zeroIndex) orientationLineColor else axisColor,
|
if (zeroIndex) axisColor else orientationLineColor,
|
||||||
if (index != zeroIndex) orientationLineWidth else axisLineWidth
|
if (zeroIndex) axisLineWidth else orientationLineWidth
|
||||||
)
|
)
|
||||||
if (index == zeroIndex) {
|
if (zeroIndex) {
|
||||||
yAxisYPosition = yPos + labelHeight / 2
|
yAxisYPosition = yPos + labelHeight / 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw x-axis labels
|
// Draw x-axis labels
|
||||||
val xLabelsAsLabels =
|
val lastXAxisLabelWidth = xLabelsAsLabels.last().width
|
||||||
xLabels.map { Label(it.toString(), Label.LabelStyle(Fonts.font, axisLabelColor)) }
|
|
||||||
val lastXAxisLabelWidth = xLabelsAsLabels[xLabelsAsLabels.size - 1].width
|
|
||||||
val xAxisLabelMinX =
|
val xAxisLabelMinX =
|
||||||
widestYLabelWidth + axisToLabelPadding + axisLineWidth / 2
|
widestYLabelWidth + axisToLabelPadding + axisLineWidth / 2
|
||||||
val xAxisLabelMaxX = chartWidth - lastXAxisLabelWidth / 2
|
val xAxisLabelMaxX = width - lastXAxisLabelWidth / 2
|
||||||
val xAxisLabelXRange = xAxisLabelMaxX - xAxisLabelMinX
|
val xAxisLabelXRange = xAxisLabelMaxX - xAxisLabelMinX
|
||||||
xLabels.forEachIndexed { index, labelAsInt ->
|
xLabelsAsLabels.forEachIndexed { index, label ->
|
||||||
val label = Label(labelAsInt.toString(), Label.LabelStyle(Fonts.font, axisLabelColor))
|
|
||||||
val xPos = xAxisLabelMinX + index * (xAxisLabelXRange / (xLabels.size - 1))
|
val xPos = xAxisLabelMinX + index * (xAxisLabelXRange / (xLabels.size - 1))
|
||||||
label.setPosition(xPos - label.width / 2, 0f)
|
label.setPosition(xPos - label.width / 2, 0f)
|
||||||
label.draw(batch, 1f)
|
addActor(label)
|
||||||
|
|
||||||
// Draw x-axis orientation lines and y-axis
|
// Draw x-axis orientation lines and y-axis
|
||||||
drawLine(
|
drawLine(
|
||||||
batch,
|
|
||||||
xPos,
|
xPos,
|
||||||
labelHeight + axisToLabelPadding + axisLineWidth,
|
labelHeight + axisToLabelPadding + axisLineWidth,
|
||||||
xPos,
|
xPos,
|
||||||
chartHeight,
|
height,
|
||||||
if (index > 0) orientationLineColor else axisColor,
|
if (index > 0) orientationLineColor else axisColor,
|
||||||
if (index >0) orientationLineWidth else axisLineWidth
|
if (index > 0) orientationLineWidth else axisLineWidth
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw line charts for each color
|
// Draw line charts for each color
|
||||||
val linesMinX = widestYLabelWidth + axisToLabelPadding + axisLineWidth
|
val linesMinX = widestYLabelWidth + axisToLabelPadding + axisLineWidth
|
||||||
val linesMaxX = chartWidth - lastXAxisLabelWidth / 2
|
val linesMaxX = width - lastXAxisLabelWidth / 2
|
||||||
val linesMinY = yAxisYPosition
|
val linesMinY = yAxisYPosition
|
||||||
val linesMaxY = chartHeight - labelHeight / 2
|
val linesMaxY = height - labelHeight / 2
|
||||||
val scaleX = (linesMaxX - linesMinX) / xLabels.max()
|
val scaleX = (linesMaxX - linesMinX) / (xLabels.max() - xLabels.min())
|
||||||
val scaleY = (linesMaxY - linesMinY) / yLabels.max()
|
val scaleY = (linesMaxY - linesMinY) / (yLabels.max() - yLabels.min())
|
||||||
val negativeScaleY = if (hasNegativeYValues) (linesMinY - negativeOrientationLineYPosition) / -negativeYLabel else 0f
|
val negativeOrientationLineYPosition = yAxisLabelMinY + labelHeight / 2
|
||||||
|
val minXLabel = xLabels.min()
|
||||||
|
val minYLabel = yLabels.min()
|
||||||
|
val negativeScaleY = (negativeOrientationLineYPosition - linesMinY) / if (minYLabel < 0) minYLabel else 1
|
||||||
val sortedPoints = dataPoints.sortedBy { it.x }
|
val sortedPoints = dataPoints.sortedBy { it.x }
|
||||||
val pointsByCiv = sortedPoints.groupBy { it.civ }
|
val pointsByCiv = sortedPoints.groupBy { it.civ }
|
||||||
// We want the current player civ to be drawn last, so it is never overlapped by another player.
|
// We want the current player civ to be drawn last, so it is never overlapped by another player.
|
||||||
@ -191,8 +237,10 @@ class LineChart(
|
|||||||
for (civ in civIterationOrder) {
|
for (civ in civIterationOrder) {
|
||||||
val points = pointsByCiv[civ]!!
|
val points = pointsByCiv[civ]!!
|
||||||
val scaledPoints : List<DataPoint<Float>> = points.map {
|
val scaledPoints : List<DataPoint<Float>> = points.map {
|
||||||
val yScale = if (it.y < 0f) negativeScaleY else scaleY
|
if (it.y < 0f)
|
||||||
DataPoint(linesMinX + it.x * scaleX, linesMinY + it.y * yScale, it.civ)
|
DataPoint(linesMinX + (it.x - minXLabel) * scaleX, linesMinY + it.y * negativeScaleY, it.civ)
|
||||||
|
else
|
||||||
|
DataPoint(linesMinX + (it.x - minXLabel) * scaleX, linesMinY + (it.y - minYLabel) * scaleY, it.civ)
|
||||||
}
|
}
|
||||||
// Probably nobody can tell the difference of one pixel, so that seems like a reasonable epsilon.
|
// Probably nobody can tell the difference of one pixel, so that seems like a reasonable epsilon.
|
||||||
val simplifiedScaledPoints = douglasPeucker(scaledPoints, 1f)
|
val simplifiedScaledPoints = douglasPeucker(scaledPoints, 1f)
|
||||||
@ -205,7 +253,7 @@ class LineChart(
|
|||||||
val selectedCivBackgroundColor =
|
val selectedCivBackgroundColor =
|
||||||
if (useActualColor(civ)) civ.nation.getInnerColor() else Color.LIGHT_GRAY
|
if (useActualColor(civ)) civ.nation.getInnerColor() else Color.LIGHT_GRAY
|
||||||
drawLine(
|
drawLine(
|
||||||
batch, a.x, a.y, b.x, b.y,
|
a.x, a.y, b.x, b.y,
|
||||||
selectedCivBackgroundColor, chartLineWidth * 3
|
selectedCivBackgroundColor, chartLineWidth * 3
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -214,31 +262,19 @@ class LineChart(
|
|||||||
val a = simplifiedScaledPoints[i - 1]
|
val a = simplifiedScaledPoints[i - 1]
|
||||||
val b = simplifiedScaledPoints[i]
|
val b = simplifiedScaledPoints[i]
|
||||||
val civLineColor = if (useActualColor(civ)) civ.nation.getOuterColor() else Color.DARK_GRAY
|
val civLineColor = if (useActualColor(civ)) civ.nation.getOuterColor() else Color.DARK_GRAY
|
||||||
drawLine(batch, a.x, a.y, b.x, b.y, civLineColor, chartLineWidth)
|
drawLine(a.x, a.y, b.x, b.y, civLineColor, chartLineWidth)
|
||||||
|
|
||||||
// Draw the selected Civ icon on its last datapoint
|
// Draw the selected Civ icon on its last datapoint
|
||||||
if (i == simplifiedScaledPoints.size - 1 && selectedCiv == civ && selectedCiv in lastTurnDataPoints) {
|
if (i == simplifiedScaledPoints.size - 1 && selectedCiv == civ && selectedCiv in lastTurnDataPoints) {
|
||||||
val selectedCivIcon =
|
val selectedCivIcon = VictoryScreenCivGroup.getCivImageAndColors(selectedCiv, viewingCiv, DefeatedPlayerStyle.REGULAR).first
|
||||||
VictoryScreenCivGroup(
|
selectedCivIcon.apply {
|
||||||
selectedCiv,
|
|
||||||
"",
|
|
||||||
viewingCiv,
|
|
||||||
DefeatedPlayerStyle.REGULAR
|
|
||||||
).children[0].run {
|
|
||||||
(this as? Image)?.surroundWithCircle(30f, color = Color.LIGHT_GRAY)
|
|
||||||
?: this
|
|
||||||
}
|
|
||||||
selectedCivIcon.run {
|
|
||||||
setPosition(b.x, b.y, Align.center)
|
setPosition(b.x, b.y, Align.center)
|
||||||
setSize(33f, 33f) // Dead Civs need this
|
setSize(33f, 33f) // Dead Civs need this
|
||||||
draw(batch, parentAlpha)
|
|
||||||
}
|
}
|
||||||
|
addActor(selectedCivIcon)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore the previous batch transformation matrix
|
|
||||||
batch.transformMatrix = oldTransformMatrix
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun useActualColor(civ: Civilization) : Boolean {
|
private fun useActualColor(civ: Civilization) : Boolean {
|
||||||
@ -260,29 +296,18 @@ class LineChart(
|
|||||||
return lastDataPoints
|
return lastDataPoints
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun drawLine(
|
private fun drawLine(x1: Float, y1: Float, x2: Float, y2: Float, lineColor: Color, width: Float) {
|
||||||
batch: Batch,
|
|
||||||
x1: Float,
|
|
||||||
y1: Float,
|
|
||||||
x2: Float,
|
|
||||||
y2: Float,
|
|
||||||
color: Color,
|
|
||||||
width: Float
|
|
||||||
) {
|
|
||||||
shapeRenderer.projectionMatrix = batch.projectionMatrix
|
|
||||||
shapeRenderer.transformMatrix = batch.transformMatrix
|
|
||||||
batch.end()
|
|
||||||
|
|
||||||
shapeRenderer.begin(ShapeRenderer.ShapeType.Filled)
|
val line = ImageGetter.getLine(x1, y1, x2, y2, width)
|
||||||
shapeRenderer.color = color
|
line.color = lineColor
|
||||||
shapeRenderer.rectLine(x1, y1, x2, y2, width)
|
addActor(line)
|
||||||
// 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()
|
|
||||||
|
|
||||||
batch.begin()
|
val edgeRounding = ImageGetter.getCircle().apply {
|
||||||
|
setSize(width, width)
|
||||||
|
color = lineColor
|
||||||
|
setPosition(x1 - width / 2f, y1 - width / 2f)
|
||||||
|
}
|
||||||
|
addActor(edgeRounding)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun douglasPeucker(points: List<DataPoint<Float>>, epsilon: Float): List<DataPoint<Float>> {
|
private fun douglasPeucker(points: List<DataPoint<Float>>, epsilon: Float): List<DataPoint<Float>> {
|
||||||
@ -352,10 +377,4 @@ class LineChart(
|
|||||||
return sqrt((dx * dx + dy * dy).toDouble()).toFloat()
|
return sqrt((dx * dx + dy * dy).toDouble()).toFloat()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getMinWidth() = chartWidth
|
|
||||||
override fun getMinHeight() = chartHeight
|
|
||||||
override fun getPrefWidth() = chartWidth
|
|
||||||
override fun getPrefHeight() = chartHeight
|
|
||||||
override fun getMaxWidth() = chartWidth
|
|
||||||
override fun getMaxHeight() = chartHeight
|
|
||||||
}
|
}
|
||||||
|
@ -2,16 +2,16 @@ package com.unciv.ui.screens.victoryscreen
|
|||||||
|
|
||||||
import com.badlogic.gdx.graphics.Color
|
import com.badlogic.gdx.graphics.Color
|
||||||
import com.badlogic.gdx.scenes.scene2d.Touchable
|
import com.badlogic.gdx.scenes.scene2d.Touchable
|
||||||
import com.badlogic.gdx.scenes.scene2d.ui.Container
|
|
||||||
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||||
import com.badlogic.gdx.utils.Align
|
import com.badlogic.gdx.utils.Align
|
||||||
import com.unciv.logic.civilization.Civilization
|
|
||||||
import com.unciv.ui.components.AutoScrollPane
|
import com.unciv.ui.components.AutoScrollPane
|
||||||
|
import com.unciv.ui.components.DataPoint
|
||||||
import com.unciv.ui.components.LineChart
|
import com.unciv.ui.components.LineChart
|
||||||
import com.unciv.ui.components.TabbedPager
|
import com.unciv.ui.components.TabbedPager
|
||||||
import com.unciv.ui.components.input.onChange
|
import com.unciv.ui.components.input.onChange
|
||||||
import com.unciv.ui.components.input.onClick
|
import com.unciv.ui.components.input.onClick
|
||||||
import com.unciv.ui.components.extensions.packIfNeeded
|
import com.unciv.ui.components.extensions.packIfNeeded
|
||||||
|
import com.unciv.ui.components.input.OnClickListener
|
||||||
import com.unciv.ui.images.ImageGetter
|
import com.unciv.ui.images.ImageGetter
|
||||||
import com.unciv.ui.screens.basescreen.BaseScreen
|
import com.unciv.ui.screens.basescreen.BaseScreen
|
||||||
import com.unciv.ui.screens.newgamescreen.TranslatedSelectBox
|
import com.unciv.ui.screens.newgamescreen.TranslatedSelectBox
|
||||||
@ -36,7 +36,9 @@ class VictoryScreenCharts(
|
|||||||
align = Align.center
|
align = Align.center
|
||||||
}
|
}
|
||||||
|
|
||||||
private val chartHolder = Container<LineChart?>(null)
|
private var lineChart = LineChart(viewingCiv)
|
||||||
|
// if it is negative - no zoom, if positive - zoom at turn X
|
||||||
|
private var zoomAtX : IntRange? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
civButtonsScroll.setScrollingDisabled(true, false)
|
civButtonsScroll.setScrollingDisabled(true, false)
|
||||||
@ -46,7 +48,14 @@ class VictoryScreenCharts(
|
|||||||
controlsColumn.add(civButtonsScroll).fillY()
|
controlsColumn.add(civButtonsScroll).fillY()
|
||||||
defaults().fill().pad(20f)
|
defaults().fill().pad(20f)
|
||||||
add(controlsColumn)
|
add(controlsColumn)
|
||||||
add(chartHolder).growX().top().padLeft(0f)
|
updateControls()
|
||||||
|
add(lineChart).growX().top().padLeft(0f)
|
||||||
|
|
||||||
|
val onChartClick = OnClickListener(function = { _ , x, _ ->
|
||||||
|
zoomAtX = if (zoomAtX == null) lineChart.getTurnAt(x) else null
|
||||||
|
updateChart()
|
||||||
|
})
|
||||||
|
lineChart.addListener(onChartClick)
|
||||||
|
|
||||||
rankingTypeSelect.onChange {
|
rankingTypeSelect.onChange {
|
||||||
rankingType = RankingType.values()
|
rankingType = RankingType.values()
|
||||||
@ -85,38 +94,31 @@ class VictoryScreenCharts(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun updateChart() {
|
private fun updateChart() {
|
||||||
// LineChart does not "cooperate" in Layout - the size we set here is final.
|
lineChart.update(getLineChartData(rankingType), selectedCiv)
|
||||||
// These values seem to fit the cell it'll be in - we subtract padding and some extra manually
|
|
||||||
packIfNeeded()
|
packIfNeeded()
|
||||||
chartHolder.actor = LineChart(
|
|
||||||
getLineChartData(rankingType),
|
|
||||||
viewingCiv,
|
|
||||||
selectedCiv,
|
|
||||||
parent.width - getColumnWidth(0) - 60f,
|
|
||||||
parent.height - 60f
|
|
||||||
)
|
|
||||||
chartHolder.invalidateHierarchy()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getLineChartData(
|
private fun getLineChartData(rankingType: RankingType): List<DataPoint<Int>> {
|
||||||
rankingType: RankingType
|
|
||||||
): Map<Int, Map<Civilization, Int>> {
|
|
||||||
return gameInfo.civilizations.asSequence()
|
return gameInfo.civilizations.asSequence()
|
||||||
.filter { it.isMajorCiv() }
|
.filter { it.isMajorCiv() }
|
||||||
.flatMap { civ ->
|
.flatMap { civ ->
|
||||||
civ.statsHistory
|
civ.statsHistory
|
||||||
|
.filterKeys { zoomAtX == null || it in zoomAtX!! }
|
||||||
.filterValues { it.containsKey(rankingType) }
|
.filterValues { it.containsKey(rankingType) }
|
||||||
.map { (turn, data) -> Pair(turn, Pair(civ, data.getValue(rankingType))) }
|
.map { (turn, data) -> Pair(turn, Pair(civ, data.getValue(rankingType))) }
|
||||||
}
|
}
|
||||||
.groupBy({ it.first }, { it.second })
|
.groupBy({ it.first }, { it.second })
|
||||||
.mapValues { group -> group.value.toMap() }
|
.mapValues { group -> group.value.toMap() }
|
||||||
|
.flatMap { turn ->
|
||||||
|
turn.value.map { (civ, value) -> DataPoint(turn.key, value, civ) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun activated(index: Int, caption: String, pager: TabbedPager) {
|
override fun activated(index: Int, caption: String, pager: TabbedPager) {
|
||||||
pager.setScrollDisabled(true)
|
pager.setScrollDisabled(true)
|
||||||
getCell(controlsColumn).height(parent.height)
|
controlsColumn.height = parent.height
|
||||||
getCell(chartHolder).height(parent.height)
|
lineChart.height = parent.height
|
||||||
if (chartHolder.actor == null) update()
|
update()
|
||||||
civButtonsTable.invalidateHierarchy()
|
civButtonsTable.invalidateHierarchy()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
package com.unciv.ui.screens.victoryscreen
|
package com.unciv.ui.screens.victoryscreen
|
||||||
|
|
||||||
import com.badlogic.gdx.graphics.Color
|
import com.badlogic.gdx.graphics.Color
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.Actor
|
||||||
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||||
import com.badlogic.gdx.utils.Align
|
import com.badlogic.gdx.utils.Align
|
||||||
import com.unciv.Constants
|
import com.unciv.Constants
|
||||||
import com.unciv.logic.civilization.Civilization
|
import com.unciv.logic.civilization.Civilization
|
||||||
import com.unciv.models.translations.tr
|
import com.unciv.models.translations.tr
|
||||||
|
import com.unciv.ui.components.extensions.setSize
|
||||||
import com.unciv.ui.components.extensions.toLabel
|
import com.unciv.ui.components.extensions.toLabel
|
||||||
import com.unciv.ui.images.ImageGetter
|
import com.unciv.ui.images.ImageGetter
|
||||||
import com.unciv.ui.screens.basescreen.BaseScreen
|
import com.unciv.ui.screens.basescreen.BaseScreen
|
||||||
@ -54,34 +56,17 @@ internal class VictoryScreenCivGroup(
|
|||||||
: this(civ, "\n", additionalInfo.tr(), currentPlayer, defeatedPlayerStyle)
|
: this(civ, "\n", additionalInfo.tr(), currentPlayer, defeatedPlayerStyle)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
var labelText = if (additionalInfo.isEmpty()) civ.civName
|
val labelText =
|
||||||
else "{${civ.civName}}$separator{$additionalInfo}"
|
if (currentPlayer.knows(civ) || currentPlayer == civ ||
|
||||||
val labelColor: Color
|
civ.isDefeated() || currentPlayer.isDefeated()) {
|
||||||
val backgroundColor: Color
|
if (additionalInfo.isEmpty()) civ.civName
|
||||||
|
else "{${civ.civName}}$separator{$additionalInfo}"
|
||||||
|
} else Constants.unknownNationName
|
||||||
|
|
||||||
when {
|
val civInfo = getCivImageAndColors(civ, currentPlayer, defeatedPlayerStyle)
|
||||||
civ.isDefeated() && defeatedPlayerStyle == DefeatedPlayerStyle.GREYED_OUT -> {
|
add(civInfo.first).size(30f)
|
||||||
add(ImageGetter.getImage("OtherIcons/DisbandUnit")).size(30f)
|
val backgroundColor = civInfo.second
|
||||||
backgroundColor = Color.LIGHT_GRAY
|
val labelColor = civInfo.third
|
||||||
labelColor = Color.BLACK
|
|
||||||
}
|
|
||||||
currentPlayer.isSpectator()
|
|
||||||
|| civ.isDefeated() && defeatedPlayerStyle == DefeatedPlayerStyle.REGULAR
|
|
||||||
|| currentPlayer == civ // || game.viewEntireMapForDebug
|
|
||||||
|| currentPlayer.knows(civ)
|
|
||||||
|| currentPlayer.isDefeated()
|
|
||||||
|| currentPlayer.victoryManager.hasWon() -> {
|
|
||||||
add(ImageGetter.getNationPortrait(civ.nation, 30f))
|
|
||||||
backgroundColor = civ.nation.getOuterColor()
|
|
||||||
labelColor = civ.nation.getInnerColor()
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
add(ImageGetter.getRandomNationPortrait(30f))
|
|
||||||
backgroundColor = Color.DARK_GRAY
|
|
||||||
labelColor = Color.WHITE
|
|
||||||
labelText = Constants.unknownNationName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
background = BaseScreen.skinStrings.getUiBackground("VictoryScreen/CivGroup", BaseScreen.skinStrings.roundedEdgeRectangleShape, backgroundColor)
|
background = BaseScreen.skinStrings.getUiBackground("VictoryScreen/CivGroup", BaseScreen.skinStrings.roundedEdgeRectangleShape, backgroundColor)
|
||||||
val label = labelText.toLabel(labelColor, hideIcons = true)
|
val label = labelText.toLabel(labelColor, hideIcons = true)
|
||||||
@ -89,4 +74,27 @@ internal class VictoryScreenCivGroup(
|
|||||||
|
|
||||||
add(label).padLeft(10f)
|
add(label).padLeft(10f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun getCivImageAndColors(civ: Civilization, currentPlayer: Civilization, defeatedPlayerStyle: DefeatedPlayerStyle): Triple<Actor, Color, Color> {
|
||||||
|
when {
|
||||||
|
civ.isDefeated() && defeatedPlayerStyle == DefeatedPlayerStyle.GREYED_OUT -> {
|
||||||
|
val icon = (ImageGetter.getImage("OtherIcons/DisbandUnit"))
|
||||||
|
icon.setSize(30f)
|
||||||
|
return Triple(icon, Color.LIGHT_GRAY, Color.BLACK)
|
||||||
|
}
|
||||||
|
currentPlayer.isSpectator()
|
||||||
|
|| civ.isDefeated() && defeatedPlayerStyle == DefeatedPlayerStyle.REGULAR
|
||||||
|
|| currentPlayer == civ // || game.viewEntireMapForDebug
|
||||||
|
|| currentPlayer.knows(civ)
|
||||||
|
|| currentPlayer.isDefeated()
|
||||||
|
|| currentPlayer.victoryManager.hasWon() -> {
|
||||||
|
return Triple(ImageGetter.getNationPortrait(civ.nation, 30f), civ.nation.getOuterColor(), civ.nation.getInnerColor())
|
||||||
|
}
|
||||||
|
else ->
|
||||||
|
return Triple((ImageGetter.getRandomNationPortrait(30f)), Color.LIGHT_GRAY, Color.BLACK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
140
tests/src/com/unciv/ui/components/LineChartTests.kt
Normal file
140
tests/src/com/unciv/ui/components/LineChartTests.kt
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
package com.unciv.ui.components
|
||||||
|
|
||||||
|
import com.unciv.logic.civilization.Civilization
|
||||||
|
import org.junit.Assert
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class LineChartTests {
|
||||||
|
|
||||||
|
private val civ = Civilization("My civ")
|
||||||
|
private val lineChart = LineChart(Civilization(civ.civName))
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `labels for an empty list are just 0 label`() {
|
||||||
|
val data = mutableListOf <DataPoint<Int>>()
|
||||||
|
val result = lineChart.generateLabels(data, true)
|
||||||
|
Assert.assertTrue(result.size == 1 && result[0] == 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `chart goes along 0 line`() {
|
||||||
|
val data = mutableListOf <DataPoint<Int>>()
|
||||||
|
data.add(DataPoint(0,0, civ))
|
||||||
|
data.add(DataPoint(1,0, civ))
|
||||||
|
data.add(DataPoint(2,0, civ))
|
||||||
|
val result = lineChart.generateLabels(data, true)
|
||||||
|
Assert.assertTrue(result.size == 2 && result[0] == 0 && result[1] == 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `chart is from 0 to the positive value`() {
|
||||||
|
val data = mutableListOf <DataPoint<Int>>()
|
||||||
|
data.add(DataPoint(0,-100, civ))
|
||||||
|
data.add(DataPoint(1,200, civ))
|
||||||
|
data.add(DataPoint(2,1600, civ))
|
||||||
|
val result = lineChart.generateLabels(data, false) // testing the X axis
|
||||||
|
Assert.assertTrue(result.size == 11 && result.first() == 0 && result[1] == 1 && result.last() == 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `chart is from 0 to the negative value`() {
|
||||||
|
val data = mutableListOf <DataPoint<Int>>()
|
||||||
|
data.add(DataPoint(0,0, civ))
|
||||||
|
data.add(DataPoint(1,-2, civ))
|
||||||
|
data.add(DataPoint(2,-6, civ))
|
||||||
|
val result = lineChart.generateLabels(data, true) // testing the Y axis
|
||||||
|
Assert.assertTrue(result.size == 11 && result.first() == -10 && result[1] == -9 && result.last() == 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `chart goes from the negative to the positive value near 0`() {
|
||||||
|
val data = mutableListOf <DataPoint<Int>>()
|
||||||
|
data.add(DataPoint(0,-5, civ))
|
||||||
|
data.add(DataPoint(1,2, civ))
|
||||||
|
data.add(DataPoint(2,6, civ))
|
||||||
|
val result = lineChart.generateLabels(data, true)
|
||||||
|
Assert.assertTrue(result.size == 21 && result.first() == -10 && result[1] == -9 && result.last() == 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `chart goes from the negative to the positive far from 0`() {
|
||||||
|
val data = mutableListOf <DataPoint<Int>>()
|
||||||
|
data.add(DataPoint(0,-59, civ))
|
||||||
|
data.add(DataPoint(1,191, civ))
|
||||||
|
data.add(DataPoint(2,160, civ))
|
||||||
|
val result = lineChart.generateLabels(data, true)
|
||||||
|
Assert.assertTrue(result.size == 14 && result.first() == -60 && result[1] == -40 && result[3] == 0 && result.last() == 200)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `chart goes from the positive to the negative far from 0`() {
|
||||||
|
val data = mutableListOf <DataPoint<Int>>()
|
||||||
|
data.add(DataPoint(0,-485, civ))
|
||||||
|
data.add(DataPoint(1,191, civ))
|
||||||
|
data.add(DataPoint(2,160, civ))
|
||||||
|
val result = lineChart.generateLabels(data, true)
|
||||||
|
Assert.assertTrue(result.size == 15 && result.first() == -500 && result[1] == -450 && result[10] == 0 && result.last() == 200)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `chart is within the positive values far from 0`() {
|
||||||
|
val data = mutableListOf <DataPoint<Int>>()
|
||||||
|
data.add(DataPoint(0,290, civ))
|
||||||
|
data.add(DataPoint(1,1159, civ))
|
||||||
|
data.add(DataPoint(2,280, civ))
|
||||||
|
val result = lineChart.generateLabels(data, true)
|
||||||
|
Assert.assertTrue(result.size == 11 && result.first() == 200 && result[1] == 300 && result.last() == 1200)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `chart is within the negative values far from 0`() {
|
||||||
|
val data = mutableListOf <DataPoint<Int>>()
|
||||||
|
data.add(DataPoint(0,-290, civ))
|
||||||
|
data.add(DataPoint(1,-1160, civ))
|
||||||
|
data.add(DataPoint(2,-280, civ))
|
||||||
|
val result = lineChart.generateLabels(data, true)
|
||||||
|
Assert.assertTrue(result.size == 10 && result.first() == -1200 && result[1] == -1080 && result.last() == -120)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `chart is within the positive values near 0`() {
|
||||||
|
val data = mutableListOf <DataPoint<Int>>()
|
||||||
|
data.add(DataPoint(0,180, civ))
|
||||||
|
data.add(DataPoint(1,1101, civ))
|
||||||
|
data.add(DataPoint(2,980, civ))
|
||||||
|
val result = lineChart.generateLabels(data, true)
|
||||||
|
Assert.assertTrue(result.size == 11 && result.first() == 0 && result[1] == 120 && result.last() == 1200)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `chart is within the negative values near 0`() {
|
||||||
|
val data = mutableListOf <DataPoint<Int>>()
|
||||||
|
data.add(DataPoint(0,-180, civ))
|
||||||
|
data.add(DataPoint(1,-1101, civ))
|
||||||
|
data.add(DataPoint(2,-980, civ))
|
||||||
|
val result = lineChart.generateLabels(data, true)
|
||||||
|
Assert.assertTrue(result.size == 11 && result.first() == -1200 && result[1] == -1080 && result.last() == 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `chart is mostly in the positive values but not only`() {
|
||||||
|
val data = mutableListOf <DataPoint<Int>>()
|
||||||
|
data.add(DataPoint(0,210, civ))
|
||||||
|
data.add(DataPoint(1,1101, civ))
|
||||||
|
data.add(DataPoint(2,-89, civ))
|
||||||
|
val result = lineChart.generateLabels(data, true)
|
||||||
|
Assert.assertTrue(result.size == 12 && result.first() == -90 && result[1] == 0 && result.last() == 1200)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `chart is mostly in the negative values but not only`() {
|
||||||
|
val data = mutableListOf <DataPoint<Int>>()
|
||||||
|
data.add(DataPoint(0,111, civ))
|
||||||
|
data.add(DataPoint(1,-2101, civ))
|
||||||
|
data.add(DataPoint(2,-345, civ))
|
||||||
|
val result = lineChart.generateLabels(data, true)
|
||||||
|
Assert.assertTrue(result.size == 12 && result.first() == -2200 && result[10] == 0 && result.last() == 200)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user