diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/BarCardView.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/BarCardView.kt index fa3122ed3..12ceb49d7 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/BarCardView.kt +++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/BarCardView.kt @@ -40,6 +40,7 @@ class BarCardView(context: Context, attrs: AttributeSet) : LinearLayout(context, val androidColor = state.theme.color(state.color).toInt() binding.chart.view = BarChart(state.theme, JavaLocalDateFormatter(Locale.US)).apply { series = mutableListOf(state.entries.map { it.value / 1000.0 }) + skippedSeries = mutableListOf(state.skippedEntries.map { it.value / 1.0 }) colors = mutableListOf(theme.color(state.color.paletteIndex)) axis = state.entries.map { it.timestamp.toLocalDate() } } diff --git a/uhabits-core/assets/test/views/BarChart/base.png b/uhabits-core/assets/test/views/BarChart/base.png index e800b1c84..16e58d5ca 100644 Binary files a/uhabits-core/assets/test/views/BarChart/base.png and b/uhabits-core/assets/test/views/BarChart/base.png differ diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/BarCard.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/BarCard.kt index 4c1f73133..ed859a376 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/BarCard.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/BarCard.kt @@ -22,6 +22,7 @@ package org.isoron.uhabits.core.ui.screens.habits.show.views import org.isoron.uhabits.core.models.Entry import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.PaletteColor +import org.isoron.uhabits.core.models.countSkippedDays import org.isoron.uhabits.core.models.groupedSum import org.isoron.uhabits.core.preferences.Preferences import org.isoron.uhabits.core.ui.views.Theme @@ -33,6 +34,7 @@ data class BarCardState( val bucketSize: Int, val color: PaletteColor, val entries: List, + val skippedEntries: List, val isNumerical: Boolean, val numericalSpinnerPosition: Int, ) @@ -64,9 +66,17 @@ class BarCardPresenter( firstWeekday = firstWeekday, isNumerical = habit.isNumerical, ) + // get all data from the oldest date till today, then count skipped days, and + // finally group days according to the truncate value (by weeks for example) + val skippedEntries = habit.computedEntries.getByInterval(oldest, today).countSkippedDays( + truncateField = ScoreCardPresenter.getTruncateField(bucketSize), + firstWeekday = firstWeekday + ) + return BarCardState( theme = theme, entries = entries, + skippedEntries = skippedEntries, bucketSize = bucketSize, color = habit.color, isNumerical = habit.isNumerical, diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/views/BarChart.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/views/BarChart.kt index 493ec68dc..72fde8716 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/views/BarChart.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/views/BarChart.kt @@ -36,6 +36,7 @@ class BarChart( // Data var series = mutableListOf>() + var skippedSeries = mutableListOf>() var colors = mutableListOf() var axis = listOf() override var dataOffset = 0 @@ -83,28 +84,64 @@ class BarChart( dataColumn < 0 || dataColumn >= series[s].size -> 0.0 else -> series[s][dataColumn] } - if (value <= 0) return + val skippedValue = when { + dataColumn < 0 || dataColumn >= series[s].size -> 0.0 + else -> skippedSeries[s][dataColumn] + } + + if (value <= 0 && skippedValue <= 0.0) return // no value to be drawn val perc = value / maxValue + val skipPerc = skippedValue / maxValue val barHeight = round(maxBarHeight * perc) + val skipBarHeight = round(maxBarHeight * skipPerc) val x = barOffset(c, s) val y = height - footerHeight - barHeight + val skipY = y - skipBarHeight canvas.setColor(colors[s]) val r = round(barWidth * 0.15) if (2 * r < barHeight) { + // draw the main column canvas.fillRect(x, y + r, barWidth, barHeight - r) - canvas.fillRect(x + r, y, barWidth - 2 * r, r + 1) - canvas.fillCircle(x + r, y + r, r) - canvas.fillCircle(x + barWidth - r, y + r, r) } else { canvas.fillRect(x, y, barWidth, barHeight) } + + // draw the skipped part + if (skippedValue > 0) { + // make the skipped day square color lighter by the half + canvas.setColor(colors[s].blendWith(theme.cardBackgroundColor, 0.5)) + // if only skipped days are drawn, no separation (r) space is needed anymore + val h = if (value <= 0) skipBarHeight - r + else skipBarHeight + canvas.fillRect(x, skipY + r, barWidth, h) + } + + // draw a small (rect) on top of the column + // draw this before the skipped part to draw the grid above + val rectY: Double + if (skippedValue > 0) { + rectY = skipY + canvas.setColor(colors[s].blendWith(theme.cardBackgroundColor, 0.5)) + } else { + rectY = y + canvas.setColor(colors[s]) + } + canvas.fillRect(x + r, rectY, barWidth - 2 * r, r + 1) + // draw two circles to make the column top rounded on both sides + canvas.fillCircle(x + r, rectY + r, r) + canvas.fillCircle(x + barWidth - r, rectY + r, r) + + // draw the number above the column canvas.setFontSize(theme.smallTextSize) canvas.setTextAlign(TextAlign.CENTER) canvas.setColor(colors[s]) + val textY: Double = if (skippedValue > 0) skipY + else y + val textValue = value + skippedValue canvas.drawText( - value.toShortString(), + textValue.toShortString(), x + barWidth / 2, - y - theme.smallTextSize * 0.80 + textY - theme.smallTextSize * 0.80 ) } diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/views/BarChartTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/views/BarChartTest.kt index ff608a49b..55288c08c 100644 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/views/BarChartTest.kt +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/ui/views/BarChartTest.kt @@ -33,11 +33,13 @@ class BarChartTest { val theme = LightTheme() val component = BarChart(theme, fmt) private val axis = (0..100).map { today.minus(it) } - private val series1 = listOf(200.0, 0.0, 150.0, 137.0, 0.0, 0.0, 500.0, 30.0, 100.0, 0.0, 300.0) + private val series1 = listOf(200.0, 0.0, 150.0, 137.0, 0.0, 0.0, 500.0, 30.0, 100.0, 0.0, 300.0) + private val series2 = listOf(10.0, 50.0, 50.0, 0.0, 0.0, 0.0, 0.0, 0.0, 60.0, 0.0, 110.0) init { component.axis = axis component.series.add(series1) + component.skippedSeries.add(series2) component.colors.add(theme.color(8)) }