Significantly improve chart clarity in tooltips
This commit is contained in:
parent
46d04a4217
commit
7664b0b4de
@ -21,6 +21,7 @@ import org.lwjgl.opengl.GL11.GL_LESS
|
||||
import ru.dbotthepony.kommons.math.RGBAColor
|
||||
import ru.dbotthepony.mc.otm.client.minecraft
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.math.acos
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.pow
|
||||
@ -287,10 +288,31 @@ fun renderChart(
|
||||
val font = levelLabels.font ?: minecraft.font
|
||||
|
||||
for ((level, label) in levelLabels.labels) {
|
||||
var conflictingTop = false
|
||||
var conflictingBottom = false
|
||||
|
||||
levelLabels.labels.keys.forEach {
|
||||
if (it != level) {
|
||||
conflictingTop = conflictingTop || it > level && (it - level).absoluteValue * height < font.lineHeight.toFloat()
|
||||
conflictingBottom = conflictingBottom || it < level && (it - level).absoluteValue * height < font.lineHeight.toFloat()
|
||||
}
|
||||
}
|
||||
|
||||
val y0 = y + (1f - level) * height
|
||||
|
||||
val tX = x + (levelLabels.textGravity.repositionX(width - 1f, font.width(label).toFloat() * textScale - 1f))
|
||||
val tY = y0 - lineWidth - 1f - font.lineHeight * textScale + levelLabels.textGravity.repositionY(font.lineHeight * 2f * textScale + lineWidth + 2f, font.lineHeight.toFloat() * textScale) + 1f
|
||||
var tY = y0 + levelLabels.textGravity.repositionY(font.lineHeight * 2f * textScale + lineWidth + 2f, font.lineHeight.toFloat() * textScale)
|
||||
|
||||
if (conflictingTop && conflictingBottom) {
|
||||
// do nothing, draw at center
|
||||
tY -= 2f + lineWidth
|
||||
} else if (conflictingTop) {
|
||||
// draw at bottom
|
||||
tY += lineWidth + 2f
|
||||
} else {
|
||||
// draw at top
|
||||
tY -= font.lineHeight * textScale + lineWidth
|
||||
}
|
||||
|
||||
font.draw(
|
||||
poseStack,
|
||||
|
@ -50,7 +50,6 @@ object ClientConfig : AbstractConfig("client", ModConfig.Type.CLIENT) {
|
||||
|
||||
var CHARTS_IN_TOOLTIPS: Boolean by builder
|
||||
.comment("Draw charts in storage tooltips instead of list of last 20 values.")
|
||||
.comment("Disable to get EnderIO-like experience")
|
||||
.define("CHARTS_IN_TOOLTIPS", true)
|
||||
|
||||
init {
|
||||
|
@ -1,10 +1,8 @@
|
||||
package ru.dbotthepony.mc.otm.core.chart
|
||||
|
||||
import com.google.common.collect.ImmutableList
|
||||
import com.mojang.serialization.Codec
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import ru.dbotthepony.mc.otm.core.collect.reduce
|
||||
import ru.dbotthepony.mc.otm.core.immutableList
|
||||
import ru.dbotthepony.mc.otm.core.math.Decimal
|
||||
import ru.dbotthepony.mc.otm.data.DecimalCodec
|
||||
import ru.dbotthepony.mc.otm.network.MatteryStreamCodec
|
||||
@ -33,28 +31,4 @@ class DecimalHistoryChart : AbstractHistoryChart<Decimal> {
|
||||
get() = DecimalCodec
|
||||
override val streamCodec: MatteryStreamCodec<RegistryFriendlyByteBuf, Decimal>
|
||||
get() = DecimalCodec.NETWORK
|
||||
|
||||
companion object {
|
||||
private val HISTORY_WEIGHTERS: ImmutableList<Decimal> = immutableList {
|
||||
/*for (i in 0 until 20) {
|
||||
accept(Decimal(0.5.pow(i + 1)))
|
||||
}*/
|
||||
|
||||
val average = Decimal(1) / Decimal(20)
|
||||
|
||||
for (i in 0 until 20) {
|
||||
accept(average)
|
||||
}
|
||||
}
|
||||
|
||||
fun calcWeightedAverage(getter: (Int) -> Decimal): Decimal {
|
||||
var result = Decimal.ZERO
|
||||
|
||||
for (i in 0 until 20) {
|
||||
result += getter(i) * HISTORY_WEIGHTERS[i]
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,8 @@
|
||||
package ru.dbotthepony.mc.otm.core.chart
|
||||
|
||||
import ru.dbotthepony.mc.otm.core.math.Cluster
|
||||
import java.util.random.RandomGenerator
|
||||
|
||||
/**
|
||||
* Common interface for reading chart values
|
||||
*/
|
||||
|
175
src/main/kotlin/ru/dbotthepony/mc/otm/core/math/Clustering.kt
Normal file
175
src/main/kotlin/ru/dbotthepony/mc/otm/core/math/Clustering.kt
Normal file
@ -0,0 +1,175 @@
|
||||
package ru.dbotthepony.mc.otm.core.math
|
||||
|
||||
import ru.dbotthepony.mc.otm.core.collect.filter
|
||||
import ru.dbotthepony.mc.otm.core.collect.map
|
||||
import ru.dbotthepony.mc.otm.core.collect.toList
|
||||
import ru.dbotthepony.mc.otm.core.random
|
||||
import java.util.random.RandomGenerator
|
||||
|
||||
interface Cluster<V : Any> {
|
||||
val values: List<V>
|
||||
val center: V
|
||||
|
||||
data class Impl<V : Any>(override val values: List<V>, override val center: V) : Cluster<V>
|
||||
}
|
||||
|
||||
private class ClusterValue<V : Comparable<V>>(val value: V, var cluster: MutableCluster<V>, var error: V) {
|
||||
inline fun updateError(abs: (V) -> V, minus: (V, V) -> V) {
|
||||
error = abs(minus(cluster.center, value))
|
||||
}
|
||||
|
||||
inline fun maybeSwitchCluster(cluster: MutableCluster<V>, abs: (V) -> V, minus: (V, V) -> V): Boolean {
|
||||
if (cluster == this.cluster) return false
|
||||
|
||||
val newError = abs(minus(cluster.center, value))
|
||||
|
||||
if (newError < error) {
|
||||
error = newError
|
||||
this.cluster.values.remove(this)
|
||||
cluster.values.add(this)
|
||||
this.cluster = cluster
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class MutableCluster<V : Comparable<V>>(var center: V) {
|
||||
val values = ArrayList<ClusterValue<V>>()
|
||||
|
||||
inline fun calculateCenter(identity: V, plus: (V, V) -> V, minus: (V, V) -> V, divInt: (V, Int) -> V, abs: (V) -> V): Boolean {
|
||||
if (values.isEmpty()) return false
|
||||
|
||||
var value = identity
|
||||
values.forEach { value = plus(value, it.value) }
|
||||
val old = center
|
||||
center = divInt(value, values.size)
|
||||
|
||||
if (old != center) {
|
||||
values.forEach { it.updateError(abs, minus) }
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <V : Comparable<V>> Iterable<V>.clusterize(
|
||||
random: RandomGenerator,
|
||||
initialClusters: Int = 1,
|
||||
identity: V,
|
||||
plus: (V, V) -> V,
|
||||
minus: (V, V) -> V,
|
||||
divInt: (V, Int) -> V,
|
||||
abs: (V) -> V,
|
||||
heuristics: (min: V, max: V, error: V) -> Boolean,
|
||||
): List<Cluster<V>> {
|
||||
val itr = iterator()
|
||||
|
||||
if (!itr.hasNext())
|
||||
return listOf()
|
||||
|
||||
var min = itr.next()
|
||||
var max = min
|
||||
|
||||
while (itr.hasNext()) {
|
||||
val value = itr.next()
|
||||
|
||||
min = minOf(min, value)
|
||||
max = maxOf(max, value)
|
||||
}
|
||||
|
||||
if (min == max) {
|
||||
return listOf(Cluster.Impl(listOf(min), min))
|
||||
}
|
||||
|
||||
var targetClusters = initialClusters
|
||||
|
||||
while (true) {
|
||||
val clusters = ArrayList<MutableCluster<V>>()
|
||||
val values = ArrayList<ClusterValue<V>>()
|
||||
var converged = false
|
||||
var overSaturated = false
|
||||
|
||||
for (i in 0 until targetClusters) {
|
||||
clusters.add(MutableCluster(identity))
|
||||
}
|
||||
|
||||
for (value in this) {
|
||||
val cluster = clusters.random(random)
|
||||
val wrapped = ClusterValue(value, cluster, value)
|
||||
values.add(wrapped)
|
||||
cluster.values.add(wrapped)
|
||||
}
|
||||
|
||||
clusters.forEach { it.calculateCenter(identity, plus, minus, divInt, abs) }
|
||||
|
||||
while (!converged) {
|
||||
converged = true
|
||||
|
||||
for (value in values) {
|
||||
clusters.forEach { converged = !value.maybeSwitchCluster(it, abs, minus) && converged }
|
||||
}
|
||||
|
||||
if (!converged) {
|
||||
val emptyClusters = ArrayList<MutableCluster<V>>()
|
||||
val citr = clusters.iterator()
|
||||
|
||||
for (cluster in citr) {
|
||||
if (!cluster.calculateCenter(identity, plus, minus, divInt, abs)) {
|
||||
emptyClusters.add(cluster)
|
||||
citr.remove()
|
||||
}
|
||||
}
|
||||
|
||||
for (cluster in emptyClusters) {
|
||||
var candidate: ClusterValue<V>? = null
|
||||
|
||||
for (eCluser in clusters) {
|
||||
if (eCluser.values.size > 1) {
|
||||
for (value in eCluser.values) {
|
||||
if (candidate == null || candidate.error < value.error) {
|
||||
candidate = value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (candidate != null) {
|
||||
cluster.values.add(candidate)
|
||||
candidate.cluster.values.remove(candidate)
|
||||
candidate.cluster = cluster
|
||||
cluster.center = candidate.value
|
||||
candidate.error = identity
|
||||
clusters.add(cluster)
|
||||
} else {
|
||||
overSaturated = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val maxError = values.maxOf { it.error }
|
||||
|
||||
if (!overSaturated && targetClusters < values.size && heuristics(min, max, maxError)) {
|
||||
targetClusters++
|
||||
} else {
|
||||
return clusters.iterator()
|
||||
.filter { it.values.isNotEmpty() }
|
||||
.map { Cluster.Impl(it.values.map { it.value }, it.center) }
|
||||
.toList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val DECIMAL_ERROR_TOLERANCE = Decimal("0.04")
|
||||
|
||||
fun Iterable<Decimal>.clusterize(random: RandomGenerator, clusters: Int? = null): List<Cluster<Decimal>> {
|
||||
return clusterize(random, clusters ?: 1, Decimal.ZERO, Decimal::plus, Decimal::minus, Decimal::div, Decimal::absoluteValue) { min, max, error ->
|
||||
if (clusters != null)
|
||||
false
|
||||
else
|
||||
error / (max - min) >= DECIMAL_ERROR_TOLERANCE
|
||||
}
|
||||
}
|
@ -15,10 +15,10 @@ import ru.dbotthepony.mc.otm.config.ClientConfig
|
||||
import ru.dbotthepony.mc.otm.core.TextComponent
|
||||
import ru.dbotthepony.mc.otm.core.TranslatableComponent
|
||||
import ru.dbotthepony.mc.otm.core.math.Decimal
|
||||
import ru.dbotthepony.mc.otm.core.math.clusterize
|
||||
import ru.dbotthepony.mc.otm.core.math.isNegative
|
||||
import ru.dbotthepony.mc.otm.core.math.isZero
|
||||
import ru.dbotthepony.mc.otm.menu.widget.IProfiledLevelGaugeWidget
|
||||
import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget
|
||||
import java.math.BigInteger
|
||||
import java.util.function.BooleanSupplier
|
||||
import kotlin.math.absoluteValue
|
||||
@ -314,34 +314,34 @@ private fun formatHistoryChart(
|
||||
if (widget.received[it].isInfinite && widget.transferred[it].isInfinite)
|
||||
0.5f
|
||||
else if (diff[it].isInfinite && diff[it].isNegative)
|
||||
0.1f
|
||||
0f
|
||||
else if (diff[it].isInfinite && diff[it].isPositive)
|
||||
0.9f
|
||||
1f
|
||||
else
|
||||
0.5f
|
||||
}
|
||||
|
||||
received = FloatArray(diff.size) {
|
||||
if (widget.received[it].isInfinite) 0.9f else 0.5f
|
||||
if (widget.received[it].isInfinite) 1f else 0.5f
|
||||
}
|
||||
|
||||
transferred = FloatArray(diff.size) {
|
||||
if (widget.transferred[it].isInfinite) 0.1f else 0.5f
|
||||
if (widget.transferred[it].isInfinite) 0f else 0.5f
|
||||
}
|
||||
|
||||
labels = ChartLevelLabels(
|
||||
labels = mapOf(
|
||||
0.5f to Decimal.ZERO.formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias),
|
||||
0.1f to TextComponent("-∞"),
|
||||
0.9f to TextComponent("∞"),
|
||||
0f to TextComponent("-∞"),
|
||||
1f to TextComponent("∞"),
|
||||
)
|
||||
)
|
||||
} else if (hasPositiveInfinity) {
|
||||
normalizedDiff = FloatArray(diff.size) {
|
||||
if (diff[it].isInfinite)
|
||||
0.9f
|
||||
1f
|
||||
else
|
||||
0.5f
|
||||
0f
|
||||
}
|
||||
|
||||
received = FloatArray(diff.size) {
|
||||
@ -353,16 +353,16 @@ private fun formatHistoryChart(
|
||||
|
||||
labels = ChartLevelLabels(
|
||||
labels = mapOf(
|
||||
0.5f to Decimal.ZERO.formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias),
|
||||
0.9f to TextComponent("∞"),
|
||||
0f to Decimal.ZERO.formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias),
|
||||
1f to TextComponent("∞"),
|
||||
)
|
||||
)
|
||||
} else if (hasNegativeInfinity) {
|
||||
normalizedDiff = FloatArray(diff.size) {
|
||||
if (diff[it].isInfinite)
|
||||
0.1f
|
||||
0f
|
||||
else
|
||||
0.5f
|
||||
1f
|
||||
}
|
||||
|
||||
received = FloatArray(diff.size)
|
||||
@ -374,17 +374,16 @@ private fun formatHistoryChart(
|
||||
|
||||
labels = ChartLevelLabels(
|
||||
labels = mapOf(
|
||||
0.5f to Decimal.ZERO.formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias),
|
||||
0.1f to TextComponent("-∞"),
|
||||
1f to Decimal.ZERO.formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias),
|
||||
0f to TextComponent("-∞"),
|
||||
)
|
||||
)
|
||||
} else {
|
||||
val max = maxOf(widget.received.maxOrNull() ?: Decimal.ZERO, widget.transferred.maxOrNull() ?: Decimal.ZERO)
|
||||
val maxTransferred = widget.transferred.maxOrNull() ?: Decimal.ZERO
|
||||
val maxReceived = widget.received.maxOrNull() ?: Decimal.ZERO
|
||||
val labelNames = Float2ObjectArrayMap<Component>()
|
||||
|
||||
labelNames[0.5f] = Decimal.ZERO.formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias)
|
||||
|
||||
if (max.isZero) {
|
||||
if (maxTransferred.isZero && maxReceived.isZero) {
|
||||
normalizedDiff = FloatArray(diff.size)
|
||||
normalizedDiff.fill(0.5f)
|
||||
|
||||
@ -394,25 +393,131 @@ private fun formatHistoryChart(
|
||||
transferred = FloatArray(diff.size)
|
||||
transferred.fill(0.5f)
|
||||
|
||||
labelNames[0.1f] = TextComponent("-∞")
|
||||
labelNames[0.9f] = TextComponent("∞")
|
||||
labelNames[0f] = TextComponent("-∞")
|
||||
labelNames[0.5f] = Decimal.ZERO.formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias)
|
||||
labelNames[1f] = TextComponent("∞")
|
||||
} else {
|
||||
val zero: Float
|
||||
val transferredMult: Float
|
||||
val receivedMult: Float
|
||||
|
||||
if (maxTransferred == maxReceived) {
|
||||
zero = 0.5f
|
||||
transferredMult = 0.5f
|
||||
receivedMult = 0.5f
|
||||
} else if (maxTransferred > maxReceived) {
|
||||
val ratio = (maxReceived / maxTransferred).toFloat()
|
||||
|
||||
receivedMult = ratio * 0.5f
|
||||
zero = 1f - receivedMult
|
||||
transferredMult = 1f - receivedMult
|
||||
} else {
|
||||
val ratio = (maxTransferred / maxReceived).toFloat()
|
||||
|
||||
transferredMult = ratio * 0.5f
|
||||
zero = transferredMult
|
||||
receivedMult = 1f - transferredMult
|
||||
}
|
||||
|
||||
normalizedDiff = FloatArray(diff.size) {
|
||||
(diff[it] / max).toFloat() * 0.4f + 0.5f
|
||||
if (diff[it].isZero) {
|
||||
zero
|
||||
} else if (diff[it].isPositive) {
|
||||
zero + (diff[it] / maxReceived).toFloat() * receivedMult
|
||||
} else {
|
||||
zero + (diff[it] / maxTransferred).toFloat() * transferredMult
|
||||
}
|
||||
}
|
||||
|
||||
received = FloatArray(diff.size) {
|
||||
(widget.received[it] / max).toFloat() * 0.4f + 0.5f
|
||||
if (maxReceived.isNotZero) {
|
||||
received = FloatArray(diff.size) {
|
||||
zero + (widget.received[it] / maxReceived).toFloat() * receivedMult
|
||||
}
|
||||
} else {
|
||||
received = FloatArray(diff.size)
|
||||
received.fill(1f)
|
||||
}
|
||||
|
||||
transferred = FloatArray(diff.size) {
|
||||
(widget.transferred[it] / max).toFloat() * -0.4f + 0.5f
|
||||
if (maxTransferred.isNotZero) {
|
||||
transferred = FloatArray(diff.size) {
|
||||
zero + (widget.transferred[it] / maxTransferred).toFloat() * -transferredMult
|
||||
}
|
||||
} else {
|
||||
transferred = FloatArray(diff.size)
|
||||
transferred.fill(0f)
|
||||
}
|
||||
|
||||
labelNames[0.1f] = (-max).formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias)
|
||||
if (verbose.asBoolean) labelNames[0.3f] = (-max * Decimal.ONE_HALF).formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias)
|
||||
labelNames[0.9f] = max.formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias)
|
||||
if (verbose.asBoolean) labelNames[0.7f] = (max * Decimal.ONE_HALF).formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias)
|
||||
if (maxTransferred.isNotZero && maxReceived.isNotZero)
|
||||
labelNames[zero] = Decimal.ZERO.formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias)
|
||||
|
||||
labelNames[0f] = (-maxTransferred).formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias)
|
||||
labelNames[1f] = maxReceived.formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias)
|
||||
|
||||
val rand = java.util.Random()
|
||||
|
||||
if (maxTransferred.isNotZero && transferredMult > 0.2f) {
|
||||
for (cluster in widget.transferred.clusterize(rand)) {
|
||||
val perc = (cluster.center / maxTransferred).toFloat() * transferredMult
|
||||
|
||||
if (labelNames.keys.none { (it - perc).absoluteValue < 0.08f })
|
||||
labelNames[perc] = (-cluster.center).formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias)
|
||||
}
|
||||
}
|
||||
|
||||
if (maxReceived.isNotZero && receivedMult > 0.2f) {
|
||||
for (cluster in widget.received.clusterize(rand)) {
|
||||
val perc = zero + (cluster.center / maxReceived).toFloat() * receivedMult
|
||||
|
||||
if (labelNames.keys.none { (it - perc).absoluteValue < 0.08f })
|
||||
labelNames[perc] = cluster.center.formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias)
|
||||
}
|
||||
}
|
||||
|
||||
val clusters = diff.asIterable().clusterize(rand)
|
||||
|
||||
for (cluster in clusters) {
|
||||
val perc: Float
|
||||
|
||||
if (cluster.center.isZero)
|
||||
continue
|
||||
else if (cluster.center.isPositive)
|
||||
perc = zero + (cluster.center / maxReceived).toFloat() * receivedMult
|
||||
else
|
||||
perc = (1f + (cluster.center / maxTransferred).toFloat()) * transferredMult
|
||||
|
||||
if (labelNames.keys.none { (it - perc).absoluteValue < 0.08f })
|
||||
labelNames[perc] = cluster.center.formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias)
|
||||
}
|
||||
|
||||
/*
|
||||
if ((zero - 0.5f).absoluteValue >= 0.25f) {
|
||||
// если "ноль" далёк от центра графика, то надо попробовать добавить пограничную направляющую
|
||||
if (maxReceived > maxTransferred) {
|
||||
// пограничная на приём
|
||||
val min = widget.received.minOrNull() ?: Decimal.ZERO
|
||||
val perc = (min / maxReceived).toFloat()
|
||||
|
||||
if (min.isNotZero && perc < 0.92f) {
|
||||
labelNames[zero + perc * receivedMult] = min.formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias)
|
||||
}
|
||||
} else if (maxTransferred.isNotZero) {
|
||||
// пограничная на отдачу
|
||||
val min = widget.transferred.minOrNull() ?: Decimal.ZERO
|
||||
val perc = (min / maxTransferred).toFloat()
|
||||
|
||||
if (min.isNotZero && perc < 0.92f) {
|
||||
labelNames[perc * transferredMult] = (-min).formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// если нет, то надо попробовать добавить половинчатые направляющие
|
||||
if (transferredMult > 0.3f && (maxReceived.isZero || verbose.asBoolean && maxTransferred.isNotZero))
|
||||
labelNames[transferredMult * 0.5f] = (-maxTransferred * Decimal.ONE_HALF).formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias)
|
||||
|
||||
if (receivedMult > 0.3f && (maxTransferred.isZero || verbose.asBoolean && maxReceived.isNotZero))
|
||||
labelNames[zero + receivedMult * 0.5f] = (maxReceived * Decimal.ONE_HALF).formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias)
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
labels = ChartLevelLabels(labels = labelNames)
|
||||
@ -442,6 +547,38 @@ private fun formatHistoryChart(
|
||||
)
|
||||
}
|
||||
|
||||
result.add(Either.left(TextComponent("")))
|
||||
|
||||
run {
|
||||
val incoming = widget.received[0]
|
||||
val outgoing = widget.transferred[0]
|
||||
val delta = incoming - outgoing
|
||||
|
||||
val deltaColor = if (delta.isZero) ChatFormatting.GRAY else if (delta.isPositive) ChatFormatting.DARK_GREEN else ChatFormatting.DARK_RED
|
||||
|
||||
result.add(Either.left(TranslatableComponent(
|
||||
"otm.gui.diff",
|
||||
delta.formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias).copy().withStyle(deltaColor),
|
||||
incoming.formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias).copy().withStyle(ChatFormatting.DARK_GREEN),
|
||||
outgoing.formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias).copy().withStyle(ChatFormatting.DARK_RED),
|
||||
)))
|
||||
}
|
||||
|
||||
run {
|
||||
val incoming = widget.received.calculateAverage()
|
||||
val outgoing = widget.transferred.calculateAverage()
|
||||
val delta = incoming - outgoing
|
||||
|
||||
val deltaColor = if (delta.isZero) ChatFormatting.GRAY else if (delta.isPositive) ChatFormatting.DARK_GREEN else ChatFormatting.DARK_RED
|
||||
|
||||
result.add(Either.left(TranslatableComponent(
|
||||
"otm.gui.diff",
|
||||
delta.formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias).copy().withStyle(deltaColor),
|
||||
incoming.formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias).copy().withStyle(ChatFormatting.DARK_GREEN),
|
||||
outgoing.formatSiComponent(suffix, decimals, formatAsReadable = verbose, bias = bias).copy().withStyle(ChatFormatting.DARK_RED),
|
||||
)))
|
||||
}
|
||||
|
||||
result.add(Either.right(ChartTooltipElement(charts, if (verbose.asBoolean) 200f else 100f, if (verbose.asBoolean) 120f else 60f, levelLabels = labels)))
|
||||
}
|
||||
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 898 B |
Loading…
Reference in New Issue
Block a user