599 lines
24 KiB
Kotlin
599 lines
24 KiB
Kotlin
package de.timklge.karooheadwind.screens
|
|
|
|
import android.content.Context
|
|
import android.content.res.Configuration
|
|
import android.graphics.Bitmap
|
|
import android.graphics.Canvas
|
|
import android.graphics.Color
|
|
import android.graphics.Paint
|
|
import android.graphics.Paint.Align
|
|
import android.graphics.Path
|
|
import androidx.annotation.ColorInt
|
|
import kotlin.math.abs
|
|
import androidx.core.graphics.createBitmap
|
|
|
|
class LineGraphBuilder(val context: Context) {
|
|
enum class YAxis {
|
|
LEFT, RIGHT
|
|
}
|
|
|
|
data class DataPoint(val x: Float, val y: Float)
|
|
|
|
data class Line(
|
|
val dataPoints: List<DataPoint>,
|
|
@ColorInt val color: Int,
|
|
val label: String? = null,
|
|
val yAxis: YAxis = YAxis.LEFT, // Default to left Y-axis
|
|
val drawCircles: Boolean = true // Default to true
|
|
)
|
|
|
|
private fun isNightMode(): Boolean {
|
|
val nightModeFlags = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
|
|
return nightModeFlags == Configuration.UI_MODE_NIGHT_YES
|
|
}
|
|
|
|
fun drawLineGraph(
|
|
width: Int,
|
|
height: Int,
|
|
gridWidth: Int,
|
|
gridHeight: Int,
|
|
lines: Set<Line>,
|
|
labelProvider: ((Float) -> String)
|
|
): Bitmap {
|
|
val isNightMode = isNightMode()
|
|
|
|
val bitmap = createBitmap(width, height)
|
|
val canvas = Canvas(bitmap)
|
|
|
|
val backgroundColor = if (isNightMode) Color.BLACK else Color.WHITE
|
|
val primaryTextColor = if (isNightMode) Color.WHITE else Color.BLACK
|
|
val secondaryTextColor = if (isNightMode) Color.LTGRAY else Color.DKGRAY // For axes
|
|
|
|
canvas.drawColor(backgroundColor)
|
|
|
|
if (lines.isEmpty() || lines.all { it.dataPoints.isEmpty() }) {
|
|
val emptyPaint = Paint().apply {
|
|
color = primaryTextColor
|
|
textSize = 30f // Increased from 24f
|
|
textAlign = Align.CENTER
|
|
isAntiAlias = true
|
|
}
|
|
canvas.drawText("No data to display", width / 2f, height / 2f, emptyPaint)
|
|
return bitmap
|
|
}
|
|
|
|
val marginTop = 10f
|
|
val marginBottom = 55f // Increased from 40f
|
|
var marginRight = 25f // Increased from 20f // Made var, default updated
|
|
|
|
var dataMinX = Float.MAX_VALUE
|
|
var dataMaxX = Float.MIN_VALUE
|
|
var dataMinYLeft = Float.MAX_VALUE
|
|
var dataMaxYLeft = Float.MIN_VALUE
|
|
var dataMinYRight = Float.MAX_VALUE
|
|
var dataMaxYRight = Float.MIN_VALUE
|
|
var hasLeftYAxisData = false
|
|
var hasRightYAxisData = false
|
|
|
|
var hasData = false
|
|
lines.forEach { line ->
|
|
if (line.dataPoints.isNotEmpty()) {
|
|
hasData = true
|
|
if (line.yAxis == YAxis.LEFT) {
|
|
hasLeftYAxisData = true
|
|
line.dataPoints.forEach { point ->
|
|
dataMinX = minOf(dataMinX, point.x)
|
|
dataMaxX = maxOf(dataMaxX, point.x)
|
|
dataMinYLeft = minOf(dataMinYLeft, point.y)
|
|
dataMaxYLeft = maxOf(dataMaxYLeft, point.y)
|
|
}
|
|
} else { // YAxis.RIGHT
|
|
hasRightYAxisData = true
|
|
line.dataPoints.forEach { point ->
|
|
dataMinX = minOf(dataMinX, point.x)
|
|
dataMaxX = maxOf(dataMaxX, point.x)
|
|
dataMinYRight = minOf(dataMinYRight, point.y)
|
|
dataMaxYRight = maxOf(dataMaxYRight, point.y)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!hasData) {
|
|
val emptyPaint = Paint().apply {
|
|
color = primaryTextColor
|
|
textSize = 60f // Increased from 48f
|
|
textAlign = Align.CENTER
|
|
isAntiAlias = true
|
|
}
|
|
canvas.drawText("No data points", width / 2f, height / 2f, emptyPaint)
|
|
return bitmap
|
|
}
|
|
|
|
// Dynamically calculate marginLeft based on Y-axis label widths
|
|
val yAxisLabelPaint = Paint().apply {
|
|
textSize = 40f // Increased from 32f
|
|
isAntiAlias = true
|
|
}
|
|
|
|
var maxLabelWidthLeft = 0f
|
|
if (hasLeftYAxisData) {
|
|
val yLabelStringsLeft = mutableListOf<String>()
|
|
val numYTicksForCalc = 2 // As used later for drawing Y-axis ticks
|
|
|
|
// Determine Y-axis label strings (mirrors logic from where labels are drawn)
|
|
if (abs(dataMaxYLeft - dataMinYLeft) < 0.0001f) {
|
|
yLabelStringsLeft.add(
|
|
String.format(
|
|
java.util.Locale.getDefault(),
|
|
"%.0f",
|
|
dataMinYLeft
|
|
)
|
|
)
|
|
} else {
|
|
for (i in 0..numYTicksForCalc) {
|
|
val value =
|
|
dataMinYLeft + ((dataMaxYLeft - dataMinYLeft) / numYTicksForCalc) * i
|
|
yLabelStringsLeft.add(
|
|
String.format(
|
|
java.util.Locale.getDefault(),
|
|
"%.0f",
|
|
value
|
|
)
|
|
)
|
|
}
|
|
}
|
|
|
|
for (labelStr in yLabelStringsLeft) {
|
|
maxLabelWidthLeft =
|
|
kotlin.math.max(maxLabelWidthLeft, yAxisLabelPaint.measureText(labelStr))
|
|
}
|
|
}
|
|
|
|
val yAxisTextRightToAxisGap = 18f // Increased from 15f
|
|
val canvasEdgePadding = 3f // Increased from 5f
|
|
|
|
val dynamicMarginLeft =
|
|
if (hasLeftYAxisData) maxLabelWidthLeft + yAxisTextRightToAxisGap + canvasEdgePadding else canvasEdgePadding
|
|
|
|
// Dynamically calculate marginRight based on Right Y-axis label widths
|
|
var maxLabelWidthRight = 0f
|
|
if (hasRightYAxisData) {
|
|
val yLabelStringsRight = mutableListOf<String>()
|
|
val numYTicksForCalc = 2 // As used later for drawing Y-axis ticks
|
|
|
|
if (abs(dataMaxYRight - dataMinYRight) < 0.0001f) {
|
|
yLabelStringsRight.add(
|
|
String.format(
|
|
java.util.Locale.getDefault(),
|
|
"%.0f",
|
|
dataMinYRight
|
|
)
|
|
)
|
|
} else {
|
|
for (i in 0..numYTicksForCalc) {
|
|
val value =
|
|
dataMinYRight + ((dataMaxYRight - dataMinYRight) / numYTicksForCalc) * i
|
|
yLabelStringsRight.add(
|
|
String.format(
|
|
java.util.Locale.getDefault(),
|
|
"%.0f",
|
|
value
|
|
)
|
|
)
|
|
}
|
|
}
|
|
|
|
for (labelStr in yLabelStringsRight) {
|
|
maxLabelWidthRight =
|
|
kotlin.math.max(maxLabelWidthRight, yAxisLabelPaint.measureText(labelStr))
|
|
}
|
|
val dynamicMarginRight =
|
|
maxLabelWidthRight + yAxisTextRightToAxisGap + canvasEdgePadding
|
|
marginRight = dynamicMarginRight // Update marginRight
|
|
}
|
|
|
|
val graphWidth = width - dynamicMarginLeft - marginRight
|
|
val graphHeight = height - marginTop - marginBottom
|
|
val graphLeft = dynamicMarginLeft
|
|
val graphTop = marginTop
|
|
val graphBottom = height - marginBottom
|
|
val graphRight = width - marginRight // Define graphRight for clarity
|
|
|
|
|
|
// Legend properties
|
|
val legendTextSize = 25f
|
|
val legendTextColor = primaryTextColor
|
|
val legendPadding = 5f
|
|
val legendEntryHeight = 30f
|
|
val legendColorBoxSize = 24f
|
|
val legendTextMargin = 5f
|
|
|
|
var effectiveMinX = dataMinX
|
|
var effectiveMaxX = dataMaxX
|
|
var effectiveMinYLeft = dataMinYLeft
|
|
var effectiveMaxYLeft = dataMaxYLeft
|
|
var effectiveMinYRight = dataMinYRight
|
|
var effectiveMaxYRight = dataMaxYRight
|
|
|
|
if (dataMinX == dataMaxX) {
|
|
effectiveMinX -= 1f
|
|
effectiveMaxX += 1f
|
|
} else {
|
|
val paddingX = (dataMaxX - dataMinX) * 0.05f
|
|
if (paddingX > 0.0001f) {
|
|
effectiveMinX -= paddingX
|
|
effectiveMaxX += paddingX
|
|
} else {
|
|
effectiveMinX -= 1f
|
|
effectiveMaxX += 1f
|
|
}
|
|
}
|
|
|
|
// Y-axis Left: Adjust effective range based on new rules
|
|
if (hasLeftYAxisData) {
|
|
// effectiveMinYLeft is dataMinYLeft, effectiveMaxYLeft is dataMaxYLeft at this point
|
|
if (abs(dataMaxYLeft - dataMinYLeft) < 0.0001f) { // All Y_Left values are equal
|
|
val commonValue = dataMinYLeft
|
|
if (commonValue >= 0f) {
|
|
effectiveMinYLeft = 0f
|
|
effectiveMaxYLeft =
|
|
if (commonValue == 0f) 1f else commonValue + kotlin.math.max(
|
|
abs(commonValue * 0.1f),
|
|
1f
|
|
)
|
|
} else { // commonValue < 0f
|
|
effectiveMaxYLeft = 0f
|
|
effectiveMinYLeft = commonValue - kotlin.math.max(abs(commonValue * 0.1f), 1f)
|
|
}
|
|
} else { // Y_Left values are not all equal, apply standard 5% padding
|
|
val paddingYLeft = (dataMaxYLeft - dataMinYLeft) * 0.05f
|
|
if (paddingYLeft > 0.0001f) {
|
|
effectiveMinYLeft -= paddingYLeft // equivalent to dataMinYLeft - padding
|
|
effectiveMaxYLeft += paddingYLeft // equivalent to dataMaxYLeft + padding
|
|
} else {
|
|
effectiveMinYLeft -= 1f
|
|
effectiveMaxYLeft += 1f
|
|
}
|
|
}
|
|
// Safety check: ensure min < max for left Y-axis
|
|
if (effectiveMinYLeft >= effectiveMaxYLeft) {
|
|
effectiveMaxYLeft = effectiveMinYLeft + 1f
|
|
}
|
|
}
|
|
|
|
// Y-axis Right: Adjust effective range based on new rules
|
|
if (hasRightYAxisData) {
|
|
// effectiveMinYRight is dataMinYRight, effectiveMaxYRight is dataMaxYRight at this point
|
|
if (abs(dataMaxYRight - dataMinYRight) < 0.0001f) { // All Y_Right values are equal
|
|
val commonValue = dataMinYRight
|
|
if (commonValue >= 0f) {
|
|
effectiveMinYRight = 0f
|
|
effectiveMaxYRight =
|
|
if (commonValue == 0f) 1f else commonValue + kotlin.math.max(
|
|
abs(commonValue * 0.1f),
|
|
1f
|
|
)
|
|
} else { // commonValue < 0f
|
|
effectiveMaxYRight = 0f
|
|
effectiveMinYRight = commonValue - kotlin.math.max(abs(commonValue * 0.1f), 1f)
|
|
}
|
|
} else { // Y_Right values are not all equal, apply standard 5% padding
|
|
val paddingYRight = (dataMaxYRight - dataMinYRight) * 0.05f
|
|
if (paddingYRight > 0.0001f) {
|
|
effectiveMinYRight -= paddingYRight
|
|
effectiveMaxYRight += paddingYRight
|
|
} else {
|
|
effectiveMinYRight -= 1f
|
|
effectiveMaxYRight += 1f
|
|
}
|
|
}
|
|
// Safety check: ensure min < max for right Y-axis
|
|
if (effectiveMinYRight >= effectiveMaxYRight) {
|
|
effectiveMaxYRight = effectiveMinYRight + 1f
|
|
}
|
|
}
|
|
|
|
val rangeX =
|
|
if (abs(effectiveMaxX - effectiveMinX) < 0.0001f) 1f else (effectiveMaxX - effectiveMinX)
|
|
val rangeYLeft =
|
|
if (!hasLeftYAxisData || abs(effectiveMaxYLeft - effectiveMinYLeft) < 0.0001f) 1f else (effectiveMaxYLeft - effectiveMinYLeft)
|
|
val rangeYRight =
|
|
if (!hasRightYAxisData || abs(effectiveMaxYRight - effectiveMinYRight) < 0.0001f) 1f else (effectiveMaxYRight - effectiveMinYRight)
|
|
|
|
fun mapX(originalX: Float): Float {
|
|
return graphLeft + ((originalX - effectiveMinX) / rangeX) * graphWidth
|
|
}
|
|
|
|
fun mapYLeft(originalY: Float): Float {
|
|
return graphBottom - ((originalY - effectiveMinYLeft) / rangeYLeft) * graphHeight
|
|
}
|
|
|
|
fun mapYRight(originalY: Float): Float {
|
|
return graphBottom - ((originalY - effectiveMinYRight) / rangeYRight) * graphHeight
|
|
}
|
|
|
|
val axisPaint = Paint().apply {
|
|
color = secondaryTextColor
|
|
strokeWidth = 3f
|
|
isAntiAlias = true
|
|
}
|
|
canvas.drawLine(
|
|
graphLeft,
|
|
graphBottom,
|
|
graphLeft + graphWidth,
|
|
graphBottom,
|
|
axisPaint
|
|
) // X-axis
|
|
if (hasLeftYAxisData) {
|
|
canvas.drawLine(graphLeft, graphTop, graphLeft, graphBottom, axisPaint) // Left Y-axis
|
|
}
|
|
if (hasRightYAxisData) {
|
|
canvas.drawLine(
|
|
graphRight, // Use graphRight for clarity and consistency
|
|
graphTop,
|
|
graphRight,
|
|
graphBottom,
|
|
axisPaint
|
|
) // Right Y-axis
|
|
}
|
|
|
|
// Grid line paint
|
|
val gridLinePaint = Paint().apply {
|
|
color = if (isNightMode) Color.DKGRAY else Color.LTGRAY // Faint color
|
|
strokeWidth = 1f
|
|
isAntiAlias = true
|
|
}
|
|
|
|
val linePaint = Paint().apply {
|
|
strokeWidth = 6f
|
|
style = Paint.Style.STROKE
|
|
isAntiAlias = true
|
|
strokeCap = Paint.Cap.ROUND
|
|
strokeJoin = Paint.Join.ROUND
|
|
}
|
|
|
|
val textPaint = Paint().apply {
|
|
color = primaryTextColor
|
|
textSize = 40f // Increased from 32f
|
|
isAntiAlias = true
|
|
}
|
|
|
|
for (line in lines) {
|
|
if (line.dataPoints.isEmpty()) continue
|
|
|
|
linePaint.color = line.color
|
|
val path = Path()
|
|
val firstPoint = line.dataPoints.first()
|
|
val mapY = if (line.yAxis == YAxis.LEFT) ::mapYLeft else ::mapYRight
|
|
|
|
path.moveTo(mapX(firstPoint.x), mapY(firstPoint.y))
|
|
if (line.drawCircles) {
|
|
canvas.drawCircle(
|
|
mapX(firstPoint.x),
|
|
mapY(firstPoint.y),
|
|
8f,
|
|
linePaint.apply { style = Paint.Style.FILL })
|
|
}
|
|
linePaint.style = Paint.Style.STROKE
|
|
|
|
for (i in 1 until line.dataPoints.size) {
|
|
val point = line.dataPoints[i]
|
|
path.lineTo(mapX(point.x), mapY(point.y))
|
|
if (line.drawCircles) {
|
|
canvas.drawCircle(
|
|
mapX(point.x),
|
|
mapY(point.y),
|
|
8f,
|
|
linePaint.apply { style = Paint.Style.FILL })
|
|
}
|
|
linePaint.style = Paint.Style.STROKE
|
|
}
|
|
|
|
canvas.drawPath(path, linePaint)
|
|
}
|
|
|
|
// Draw Left Y-axis ticks and labels
|
|
if (hasLeftYAxisData) {
|
|
textPaint.textAlign = Align.RIGHT
|
|
val numYTicks = if (gridWidth > 15) 2 else 1
|
|
if (abs(dataMaxYLeft - dataMinYLeft) > 0.0001f) {
|
|
for (i in 0..numYTicks) {
|
|
val value = dataMinYLeft + ((dataMaxYLeft - dataMinYLeft) / numYTicks) * i
|
|
val yPos = mapYLeft(value)
|
|
if (yPos >= graphTop - 5f && yPos <= graphBottom + 5f) {
|
|
canvas.drawLine(graphLeft - 5f, yPos, graphLeft + 5f, yPos, axisPaint)
|
|
// Draw faint horizontal grid line
|
|
canvas.drawLine(graphLeft, yPos, graphRight, yPos, gridLinePaint)
|
|
canvas.drawText(
|
|
String.format(java.util.Locale.getDefault(), "%.0f", value),
|
|
graphLeft - 15f,
|
|
yPos + (textPaint.textSize / 3),
|
|
textPaint
|
|
)
|
|
}
|
|
}
|
|
} else {
|
|
val yPos = mapYLeft(dataMinYLeft)
|
|
canvas.drawLine(graphLeft - 5f, yPos, graphLeft + 5f, yPos, axisPaint)
|
|
// Draw faint horizontal grid line
|
|
canvas.drawLine(graphLeft, yPos, graphRight, yPos, gridLinePaint)
|
|
canvas.drawText(
|
|
String.format(java.util.Locale.getDefault(), "%.0f", dataMinYLeft),
|
|
graphLeft - 15f,
|
|
yPos + (textPaint.textSize / 3),
|
|
textPaint
|
|
)
|
|
}
|
|
}
|
|
|
|
// Draw Right Y-axis ticks and labels
|
|
if (hasRightYAxisData) {
|
|
textPaint.textAlign = Align.LEFT
|
|
val numYTicks = if (gridWidth > 15) 2 else 1
|
|
if (abs(dataMaxYRight - dataMinYRight) > 0.0001f) {
|
|
for (i in 0..numYTicks) {
|
|
val value = dataMinYRight + ((dataMaxYRight - dataMinYRight) / numYTicks) * i
|
|
val yPos = mapYRight(value)
|
|
if (yPos >= graphTop - 5f && yPos <= graphBottom + 5f) {
|
|
canvas.drawLine(
|
|
graphRight - 5f,
|
|
yPos,
|
|
graphRight + 5f,
|
|
yPos,
|
|
axisPaint
|
|
)
|
|
// Draw faint horizontal grid line
|
|
canvas.drawLine(graphLeft, yPos, graphRight, yPos, gridLinePaint)
|
|
canvas.drawText(
|
|
String.format(java.util.Locale.getDefault(), "%.0f", value),
|
|
graphRight + 15f,
|
|
yPos + (textPaint.textSize / 3),
|
|
textPaint
|
|
)
|
|
}
|
|
}
|
|
} else {
|
|
val yPos = mapYRight(dataMinYRight)
|
|
canvas.drawLine(
|
|
graphRight - 5f,
|
|
yPos,
|
|
graphRight + 5f,
|
|
yPos,
|
|
axisPaint
|
|
)
|
|
// Draw faint horizontal grid line
|
|
canvas.drawLine(graphLeft, yPos, graphRight, yPos, gridLinePaint)
|
|
canvas.drawText(
|
|
String.format(java.util.Locale.getDefault(), "%.0f", dataMinYRight),
|
|
graphRight + 15f,
|
|
yPos + (textPaint.textSize / 3),
|
|
textPaint
|
|
)
|
|
}
|
|
}
|
|
|
|
// Draw Y zero line (solid, using axisPaint)
|
|
// This is drawn after faint grid lines from Y-ticks, so it will be on top.
|
|
// It will not be drawn if it coincides with the X-axis (graphBottom), as X-axis is already solid.
|
|
val yZeroLinePaint = axisPaint // Use the same paint as other axes for consistency
|
|
|
|
if (hasLeftYAxisData) {
|
|
// If left Y-axis has data and its range includes 0
|
|
if (effectiveMinYLeft <= 0f && effectiveMaxYLeft >= 0f) {
|
|
val yZeroPos = mapYLeft(0f)
|
|
// Draw if the zero position is within graph bounds (inclusive top)
|
|
// and not effectively the same as the X-axis (graphBottom).
|
|
if (yZeroPos in graphTop..graphBottom && abs(yZeroPos - graphBottom) > 0.1f) {
|
|
canvas.drawLine(graphLeft, yZeroPos, graphRight, yZeroPos, yZeroLinePaint)
|
|
}
|
|
}
|
|
} else if (hasRightYAxisData) {
|
|
// Else, if no left Y-axis data, but right Y-axis has data and its range includes 0
|
|
if (effectiveMinYRight <= 0f && effectiveMaxYRight >= 0f) {
|
|
val yZeroPos = mapYRight(0f)
|
|
// Draw if the zero position is within graph bounds (inclusive top)
|
|
// and not effectively the same as the X-axis (graphBottom).
|
|
if (yZeroPos in graphTop..graphBottom && abs(yZeroPos - graphBottom) > 0.1f) {
|
|
canvas.drawLine(graphLeft, yZeroPos, graphRight, yZeroPos, yZeroLinePaint)
|
|
}
|
|
}
|
|
}
|
|
|
|
textPaint.textAlign = Align.CENTER
|
|
val numXTicks = if (gridHeight > 15) 3 else 1
|
|
if (abs(dataMaxX - dataMinX) > 0.0001f) {
|
|
for (i in 0..numXTicks) {
|
|
val value = dataMinX + ((dataMaxX - dataMinX) / numXTicks) * i
|
|
val xPos = mapX(value)
|
|
if (xPos >= graphLeft - 5f && xPos <= graphLeft + graphWidth + 5f) {
|
|
canvas.drawLine(xPos, graphBottom - 5f, xPos, graphBottom + 5f, axisPaint)
|
|
canvas.drawText(labelProvider(value), xPos, graphBottom + 40f, textPaint)
|
|
}
|
|
}
|
|
} else {
|
|
val xPos = mapX(dataMinX)
|
|
canvas.drawLine(xPos, graphBottom - 5f, xPos, graphBottom + 5f, axisPaint)
|
|
canvas.drawText(labelProvider(dataMinX), xPos, graphBottom + 40f, textPaint)
|
|
}
|
|
|
|
textPaint.textAlign = Align.CENTER
|
|
textPaint.color = primaryTextColor // Ensure textPaint color is reset before drawing legend
|
|
|
|
// Draw Legend
|
|
val legendPaint = Paint().apply {
|
|
textSize = legendTextSize
|
|
color = legendTextColor
|
|
isAntiAlias = true
|
|
textAlign = Align.LEFT // Important for measuring text width correctly
|
|
}
|
|
val legendColorPaint = Paint().apply {
|
|
style = Paint.Style.FILL
|
|
isAntiAlias = true
|
|
}
|
|
|
|
val legendItems = lines.filter { it.label != null }
|
|
if (legendItems.isNotEmpty()) {
|
|
var maxLegendLabelWidth = 0f
|
|
for (item in legendItems) {
|
|
maxLegendLabelWidth =
|
|
kotlin.math.max(maxLegendLabelWidth, legendPaint.measureText(item.label!!))
|
|
}
|
|
|
|
val legendContentActualLeft =
|
|
(width - marginRight - legendPadding - legendColorBoxSize - legendTextMargin - maxLegendLabelWidth)
|
|
val legendContentActualRight =
|
|
(width - marginRight - legendPadding) // Right edge of the color box
|
|
|
|
val legendContentActualTop = graphTop + legendPadding // Top edge of the first color box
|
|
val legendContentActualBottom =
|
|
legendContentActualTop + (legendItems.size - 1) * legendEntryHeight + legendColorBoxSize // Bottom edge of the last color box
|
|
|
|
val legendBgPaint = Paint().apply {
|
|
color = if (isNightMode) {
|
|
Color.argb(210, 0, 0, 0)
|
|
} else {
|
|
Color.argb(210, 255, 255, 255)
|
|
}
|
|
style = Paint.Style.FILL
|
|
isAntiAlias = true
|
|
}
|
|
canvas.drawRoundRect(
|
|
legendContentActualLeft,
|
|
legendContentActualTop,
|
|
legendContentActualRight,
|
|
legendContentActualBottom,
|
|
5f,
|
|
5f,
|
|
legendBgPaint
|
|
)
|
|
}
|
|
|
|
var currentLegendY = graphTop + legendPadding
|
|
|
|
for (line in legendItems) {
|
|
// Draw color box
|
|
legendColorPaint.color = line.color
|
|
canvas.drawRect(
|
|
width - marginRight - legendPadding - legendColorBoxSize, // left
|
|
currentLegendY, // top
|
|
width - marginRight - legendPadding, // right
|
|
currentLegendY + legendColorBoxSize, // bottom
|
|
legendColorPaint
|
|
)
|
|
|
|
// Draw label text
|
|
canvas.drawText(
|
|
line.label!!,
|
|
width - marginRight - legendPadding - legendColorBoxSize - legendTextMargin - legendPaint.measureText(
|
|
line.label
|
|
), // x: Align text to the left of the color box
|
|
currentLegendY + legendColorBoxSize / 2 + legendTextSize / 3, // y: Vertically center text with color box
|
|
legendPaint
|
|
)
|
|
currentLegendY += legendEntryHeight
|
|
}
|
|
|
|
return bitmap
|
|
}
|
|
}
|