Merge pull request #1425 from eduebernal/freq-display-normal
fix marker scaling in frequency display
|
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 8.6 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 8.6 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 22 KiB |
@@ -29,6 +29,7 @@ import org.isoron.uhabits.core.utils.DateUtils.Companion.getShortWeekdayNames
|
|||||||
import org.isoron.uhabits.core.utils.DateUtils.Companion.getStartOfTodayCalendar
|
import org.isoron.uhabits.core.utils.DateUtils.Companion.getStartOfTodayCalendar
|
||||||
import org.isoron.uhabits.core.utils.DateUtils.Companion.getStartOfTodayCalendarWithOffset
|
import org.isoron.uhabits.core.utils.DateUtils.Companion.getStartOfTodayCalendarWithOffset
|
||||||
import org.isoron.uhabits.core.utils.DateUtils.Companion.getWeekdaySequence
|
import org.isoron.uhabits.core.utils.DateUtils.Companion.getWeekdaySequence
|
||||||
|
import org.isoron.uhabits.core.utils.DateUtils.Companion.getWeekdaysInMonth
|
||||||
import org.isoron.uhabits.utils.ColorUtils.mixColors
|
import org.isoron.uhabits.utils.ColorUtils.mixColors
|
||||||
import org.isoron.uhabits.utils.StyledResources
|
import org.isoron.uhabits.utils.StyledResources
|
||||||
import org.isoron.uhabits.utils.toSimpleDataFormat
|
import org.isoron.uhabits.utils.toSimpleDataFormat
|
||||||
@@ -62,7 +63,6 @@ class FrequencyChart : ScrollableChart {
|
|||||||
private var primaryColor = 0
|
private var primaryColor = 0
|
||||||
private var isBackgroundTransparent = false
|
private var isBackgroundTransparent = false
|
||||||
private lateinit var frequency: HashMap<Timestamp, Array<Int>>
|
private lateinit var frequency: HashMap<Timestamp, Array<Int>>
|
||||||
private var maxFreq = 0
|
|
||||||
private var firstWeekday = Calendar.SUNDAY
|
private var firstWeekday = Calendar.SUNDAY
|
||||||
|
|
||||||
constructor(context: Context?) : super(context) {
|
constructor(context: Context?) : super(context) {
|
||||||
@@ -82,7 +82,6 @@ class FrequencyChart : ScrollableChart {
|
|||||||
|
|
||||||
fun setFrequency(frequency: java.util.HashMap<Timestamp, Array<Int>>) {
|
fun setFrequency(frequency: java.util.HashMap<Timestamp, Array<Int>>) {
|
||||||
this.frequency = frequency
|
this.frequency = frequency
|
||||||
maxFreq = getMaxFreq(frequency)
|
|
||||||
postInvalidate()
|
postInvalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,15 +90,6 @@ class FrequencyChart : ScrollableChart {
|
|||||||
postInvalidate()
|
postInvalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getMaxFreq(frequency: HashMap<Timestamp, Array<Int>>): Int {
|
|
||||||
var maxValue = 1
|
|
||||||
for (values in frequency.values) for (value in values) maxValue = max(
|
|
||||||
value,
|
|
||||||
maxValue
|
|
||||||
)
|
|
||||||
return maxValue
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setIsBackgroundTransparent(isBackgroundTransparent: Boolean) {
|
fun setIsBackgroundTransparent(isBackgroundTransparent: Boolean) {
|
||||||
this.isBackgroundTransparent = isBackgroundTransparent
|
this.isBackgroundTransparent = isBackgroundTransparent
|
||||||
initColors()
|
initColors()
|
||||||
@@ -166,6 +156,7 @@ class FrequencyChart : ScrollableChart {
|
|||||||
|
|
||||||
private fun drawColumn(canvas: Canvas, rect: RectF?, date: GregorianCalendar) {
|
private fun drawColumn(canvas: Canvas, rect: RectF?, date: GregorianCalendar) {
|
||||||
val values = frequency[Timestamp(date)]
|
val values = frequency[Timestamp(date)]
|
||||||
|
val weekDaysInMonth = getWeekdaysInMonth(Timestamp(date))
|
||||||
val rowHeight = rect!!.height() / 8.0f
|
val rowHeight = rect!!.height() / 8.0f
|
||||||
prevRect!!.set(rect)
|
prevRect!!.set(rect)
|
||||||
val localeWeekdayList: Array<Int> = getWeekdaySequence(firstWeekday)
|
val localeWeekdayList: Array<Int> = getWeekdaySequence(firstWeekday)
|
||||||
@@ -174,7 +165,7 @@ class FrequencyChart : ScrollableChart {
|
|||||||
rect.offset(prevRect!!.left, prevRect!!.top + baseSize * j)
|
rect.offset(prevRect!!.left, prevRect!!.top + baseSize * j)
|
||||||
val i = localeWeekdayList[j] % 7
|
val i = localeWeekdayList[j] % 7
|
||||||
if (values != null)
|
if (values != null)
|
||||||
drawMarker(canvas, rect, values[i])
|
drawMarker(canvas, rect, values[i], weekDaysInMonth[i])
|
||||||
rect.offset(0f, rowHeight)
|
rect.offset(0f, rowHeight)
|
||||||
}
|
}
|
||||||
drawFooter(canvas, rect, date)
|
drawFooter(canvas, rect, date)
|
||||||
@@ -222,7 +213,7 @@ class FrequencyChart : ScrollableChart {
|
|||||||
canvas.drawLine(rGrid.left, rGrid.top, rGrid.right, rGrid.top, pGrid!!)
|
canvas.drawLine(rGrid.left, rGrid.top, rGrid.right, rGrid.top, pGrid!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun drawMarker(canvas: Canvas, rect: RectF?, value: Int?) {
|
private fun drawMarker(canvas: Canvas, rect: RectF?, value: Int?, frequency: Int) {
|
||||||
// value can be negative when the entry is skipped
|
// value can be negative when the entry is skipped
|
||||||
val valueCopy = value?.let { max(0, it) }
|
val valueCopy = value?.let { max(0, it) }
|
||||||
|
|
||||||
@@ -230,7 +221,8 @@ class FrequencyChart : ScrollableChart {
|
|||||||
// maximal allowed mark radius
|
// maximal allowed mark radius
|
||||||
val maxRadius = (rect.height() - 2 * padding) / 2.0f
|
val maxRadius = (rect.height() - 2 * padding) / 2.0f
|
||||||
// the real mark radius is scaled down by a factor depending on the maximal frequency
|
// the real mark radius is scaled down by a factor depending on the maximal frequency
|
||||||
val scale = 1.0f / maxFreq * valueCopy!!
|
|
||||||
|
val scale = 1.0f / frequency * valueCopy!!
|
||||||
val radius = maxRadius * scale
|
val radius = maxRadius * scale
|
||||||
val colorIndex = min((colors.size - 1), ((colors.size - 1) * scale).roundToInt())
|
val colorIndex = min((colors.size - 1), ((colors.size - 1) * scale).roundToInt())
|
||||||
pGraph!!.color = colors[colorIndex]
|
pGraph!!.color = colors[colorIndex]
|
||||||
@@ -293,6 +285,5 @@ class FrequencyChart : ScrollableChart {
|
|||||||
frequency[Timestamp(date)] = values
|
frequency[Timestamp(date)] = values
|
||||||
date.add(Calendar.MONTH, -1)
|
date.add(Calendar.MONTH, -1)
|
||||||
}
|
}
|
||||||
maxFreq = getMaxFreq(frequency)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
package org.isoron.uhabits.core.utils
|
package org.isoron.uhabits.core.utils
|
||||||
|
|
||||||
import org.isoron.uhabits.core.models.Timestamp
|
import org.isoron.uhabits.core.models.Timestamp
|
||||||
|
import java.time.YearMonth
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
import java.util.Calendar.DAY_OF_MONTH
|
import java.util.Calendar.DAY_OF_MONTH
|
||||||
import java.util.Calendar.DAY_OF_WEEK
|
import java.util.Calendar.DAY_OF_WEEK
|
||||||
@@ -178,6 +179,26 @@ abstract class DateUtils {
|
|||||||
return getWeekdayNames(GregorianCalendar.SHORT, firstWeekday)
|
return getWeekdayNames(GregorianCalendar.SHORT, firstWeekday)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a vector of Int representing the frequency of each weekday in a given month.
|
||||||
|
*
|
||||||
|
* @param startOfMonth a Timestamp representing the beginning of the month.
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
fun getWeekdaysInMonth(startOfMonth: Timestamp): Array<Int> {
|
||||||
|
val month = startOfMonth.toCalendar()[Calendar.MONTH] + 1
|
||||||
|
val year = startOfMonth.toCalendar()[Calendar.YEAR]
|
||||||
|
val weekday = startOfMonth.weekday
|
||||||
|
val monthLength = YearMonth.of(year, month).lengthOfMonth()
|
||||||
|
|
||||||
|
val freq = Array(7) { 0 }
|
||||||
|
for (day in weekday until weekday + monthLength) {
|
||||||
|
freq[day % 7] += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return freq
|
||||||
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getToday(): Timestamp = Timestamp(getStartOfToday())
|
fun getToday(): Timestamp = Timestamp(getStartOfToday())
|
||||||
|
|
||||||
|
|||||||
@@ -118,6 +118,31 @@ class DateUtilsTest : BaseUnitTest() {
|
|||||||
assertThat(arrayOf("Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri"), equalTo(longWeekdayNames))
|
assertThat(arrayOf("Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri"), equalTo(longWeekdayNames))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun getWeekdaysInMonth() {
|
||||||
|
val february = GregorianCalendar(2018, Calendar.FEBRUARY, 1)
|
||||||
|
val leapFebruary = GregorianCalendar(2020, Calendar.FEBRUARY, 1)
|
||||||
|
val month = GregorianCalendar(2020, Calendar.APRIL, 1)
|
||||||
|
val longMonth = GregorianCalendar(2020, Calendar.AUGUST, 1)
|
||||||
|
|
||||||
|
assertThat(
|
||||||
|
arrayOf(4, 4, 4, 4, 4, 4, 4),
|
||||||
|
equalTo(DateUtils.getWeekdaysInMonth(Timestamp(february)))
|
||||||
|
)
|
||||||
|
assertThat(
|
||||||
|
arrayOf(5, 4, 4, 4, 4, 4, 4),
|
||||||
|
equalTo(DateUtils.getWeekdaysInMonth(Timestamp(leapFebruary)))
|
||||||
|
)
|
||||||
|
assertThat(
|
||||||
|
arrayOf(4, 4, 4, 4, 5, 5, 4),
|
||||||
|
equalTo(DateUtils.getWeekdaysInMonth(Timestamp(month)))
|
||||||
|
)
|
||||||
|
assertThat(
|
||||||
|
arrayOf(5, 5, 5, 4, 4, 4, 4),
|
||||||
|
equalTo(DateUtils.getWeekdaysInMonth(Timestamp(longMonth)))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testGetToday() {
|
fun testGetToday() {
|
||||||
setFixedLocalTime(FIXED_LOCAL_TIME)
|
setFixedLocalTime(FIXED_LOCAL_TIME)
|
||||||
|
|||||||