From e0cbc7b00ab148ee6bef6d650ca79be520722e5d Mon Sep 17 00:00:00 2001 From: Laurent Date: Wed, 10 Apr 2019 17:00:35 +0200 Subject: [PATCH 1/2] Stats and graphs: Added intelligence to provide labels for legends --- .../android/calculus/Calculator.kt | 66 +--- .../android/calculus/ComputableGroup.kt | 15 +- .../pokeranalytics/android/calculus/Stat.kt | 315 ++++++++++-------- .../android/model/interfaces/Timed.kt | 3 +- .../migrations/PokerAnalyticsMigration.kt | 1 - .../android/model/realm/Session.kt | 47 +++ .../android/model/realm/SessionSet.kt | 16 +- .../android/ui/activity/GraphActivity.kt | 4 +- .../android/ui/activity/HomeActivity.kt | 2 +- .../android/ui/fragment/GraphFragment.kt | 45 ++- .../android/ui/fragment/HistoryFragment.kt | 2 +- .../android/ui/graph/GraphExtensions.kt | 66 ++++ .../ui/view/rowrepresentable/SessionRow.kt | 1 - app/src/main/res/layout/fragment_graph.xml | 13 +- 14 files changed, 392 insertions(+), 204 deletions(-) create mode 100644 app/src/main/java/net/pokeranalytics/android/ui/graph/GraphExtensions.kt diff --git a/app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt b/app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt index 03fb0152..f3cb17b3 100644 --- a/app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt +++ b/app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt @@ -1,7 +1,5 @@ package net.pokeranalytics.android.calculus -import android.os.Parcel -import android.os.Parcelable import io.realm.Realm import net.pokeranalytics.android.calculus.Stat.* import net.pokeranalytics.android.model.realm.ComputableResult @@ -10,31 +8,6 @@ import net.pokeranalytics.android.model.realm.SessionSet import timber.log.Timber import java.util.* -class ParcelableString(var string: String) : Parcelable { - constructor(parcel: Parcel) : this(parcel.readString()) { - } - - override fun writeToParcel(parcel: Parcel, flags: Int) { - parcel.writeString(string) - } - - override fun describeContents(): Int { - return 0 - } - - companion object CREATOR : Parcelable.Creator { - override fun createFromParcel(parcel: Parcel): ParcelableString { - return ParcelableString(parcel) - } - - override fun newArray(size: Int): Array { - return arrayOfNulls(size) - } - } - -} - - /** * The class performing stats computation */ @@ -92,8 +65,6 @@ class Calculator { var computableGroups: MutableList = mutableListOf() filters.forEach { filter -> - val results = filter.results() - val sets = filter.results() val group = ComputableGroup(filter.name, filter.filterConditions.map { it.queryCondition }) computableGroups.add(group) @@ -175,20 +146,20 @@ class Calculator { tBuyinSum += computable.ratedBuyin tHands += computable.estimatedHands - val sessionId = ParcelableString(computable.session?.id ?: throw IllegalStateException("Computing lone ComputableResult")) - results.addEvolutionValue(tSum, NETRESULT, sessionId) - results.addEvolutionValue(tSum / index, AVERAGE, sessionId) - results.addEvolutionValue(index.toDouble(), NUMBER_OF_GAMES, sessionId) - results.addEvolutionValue(tBBSum / tBBSessionCount, AVERAGE_NET_BB, sessionId) - results.addEvolutionValue((tWinningSessionCount / index).toDouble(), WIN_RATIO, sessionId) - results.addEvolutionValue(tBuyinSum / index, AVERAGE_BUYIN, sessionId) + val session = computable.session ?: throw IllegalStateException("Computing lone ComputableResult") + results.addEvolutionValue(tSum, NETRESULT, session) + results.addEvolutionValue(tSum / index, AVERAGE, session) + results.addEvolutionValue(index.toDouble(), NUMBER_OF_GAMES, session) + results.addEvolutionValue(tBBSum / tBBSessionCount, AVERAGE_NET_BB, session) + results.addEvolutionValue((tWinningSessionCount / index).toDouble(), WIN_RATIO, session) + results.addEvolutionValue(tBuyinSum / index, AVERAGE_BUYIN, session) Stat.netBBPer100Hands(tBBSum, tHands)?.let { netBB100 -> - results.addEvolutionValue(netBB100, NET_BB_PER_100_HANDS, sessionId) + results.addEvolutionValue(netBB100, NET_BB_PER_100_HANDS, session) } Stat.returnOnInvestment(tSum, tBuyinSum)?.let { roi -> - results.addEvolutionValue(roi, ROI, sessionId) + results.addEvolutionValue(roi, ROI, session) } } @@ -208,7 +179,7 @@ class Calculator { val gBBSum = sessionSets.sum(SessionSet.Field.BB_NET.identifier).toDouble() val hourlyRate = gSum / gHourlyDuration -// var hourlyRateBB = gBBSum / gDuration +// var bbHourlyRate = gBBSum / gDuration when (options.evolutionValues) { Options.EvolutionValues.DATED -> { @@ -231,17 +202,16 @@ class Calculator { tHourlyRate = gSum / tHourlyDuration tHourlyRateBB = gBBSum / tHourlyDuration - val id = ParcelableString(sessionSet.id) - results.addEvolutionValue(tSum, tHourlyDuration, NETRESULT, id) - results.addEvolutionValue(tSum / tHourlyDuration, tHourlyDuration, HOURLY_RATE, id) - results.addEvolutionValue(tHourlyRate, tHourlyDuration, HOURLY_RATE, id) - results.addEvolutionValue(tIndex.toDouble(), tHourlyDuration, NUMBER_OF_SETS, id) - results.addEvolutionValue(sessionSet.netDuration.toDouble(), tHourlyDuration, DURATION, id) - results.addEvolutionValue(tHourlyDuration / tIndex, tHourlyDuration, AVERAGE_DURATION, id) - results.addEvolutionValue(tHourlyRateBB, tHourlyDuration, HOURLY_RATE_BB, id) + results.addEvolutionValue(tSum, tHourlyDuration, NETRESULT, sessionSet) + results.addEvolutionValue(tSum / tHourlyDuration, tHourlyDuration, HOURLY_RATE, sessionSet) + results.addEvolutionValue(tHourlyRate, tHourlyDuration, HOURLY_RATE, sessionSet) + results.addEvolutionValue(tIndex.toDouble(), tHourlyDuration, NUMBER_OF_SETS, sessionSet) + results.addEvolutionValue(sessionSet.netDuration.toDouble(), tHourlyDuration, DURATION, sessionSet) + results.addEvolutionValue(tHourlyDuration / tIndex, tHourlyDuration, AVERAGE_DURATION, sessionSet) + results.addEvolutionValue(tHourlyRateBB, tHourlyDuration, HOURLY_RATE_BB, sessionSet) Stat.netBBPer100Hands(gBBSum, gTotalHands)?.let { netBB100 -> - results.addEvolutionValue(netBB100, tHourlyDuration, NET_BB_PER_100_HANDS, id) + results.addEvolutionValue(netBB100, tHourlyDuration, NET_BB_PER_100_HANDS, sessionSet) } } diff --git a/app/src/main/java/net/pokeranalytics/android/calculus/ComputableGroup.kt b/app/src/main/java/net/pokeranalytics/android/calculus/ComputableGroup.kt index 57b50c37..b1fdf74f 100644 --- a/app/src/main/java/net/pokeranalytics/android/calculus/ComputableGroup.kt +++ b/app/src/main/java/net/pokeranalytics/android/calculus/ComputableGroup.kt @@ -5,6 +5,7 @@ import com.github.mikephil.charting.data.Entry import io.realm.Realm import io.realm.RealmResults import net.pokeranalytics.android.model.filter.QueryCondition +import net.pokeranalytics.android.model.interfaces.Timed import net.pokeranalytics.android.model.realm.ComputableResult import net.pokeranalytics.android.model.realm.Filter import net.pokeranalytics.android.model.realm.SessionSet @@ -113,8 +114,9 @@ class ComputedResults(group: ComputableGroup) { this._addEvolutionValue(Point(value, data), stat = stat) } - fun addEvolutionValue(value: Double, duration: Double, stat: Stat, data: Any) { - this._addEvolutionValue(Point(value, y = duration, data = data), stat = stat) + fun addEvolutionValue(value: Double, duration: Double, stat: Stat, data: Timed) { + stat.underlyingClass = data::class.java + this._addEvolutionValue(Point(value, y = duration, data = data.id), stat = stat) } private fun _addEvolutionValue(point: Point, stat: Stat) { @@ -169,7 +171,7 @@ class ComputedResults(group: ComputableGroup) { // MPAndroidChart - fun defaultStatEntries(stat: Stat): List { + fun defaultStatEntries(stat: Stat): List { return when (stat) { Stat.NUMBER_OF_SETS, Stat.NUMBER_OF_GAMES -> this.barEntries(stat) else -> this.singleLineEntries(stat) @@ -177,7 +179,7 @@ class ComputedResults(group: ComputableGroup) { } fun singleLineEntries(stat: Stat): List { - var entries = mutableListOf() + val entries = mutableListOf() this._evolutionValues[stat]?.let { points -> points.forEachIndexed { index, p -> entries.add(Entry(index.toFloat(), p.y.toFloat(), p.data)) @@ -187,7 +189,7 @@ class ComputedResults(group: ComputableGroup) { } fun durationEntries(stat: Stat): List { - var entries = mutableListOf() + val entries = mutableListOf() this._evolutionValues[stat]?.let { points -> points.forEach { p -> entries.add(Entry(p.x.toFloat(), p.y.toFloat(), p.data)) @@ -198,8 +200,7 @@ class ComputedResults(group: ComputableGroup) { fun barEntries(stat: Stat): List { - var entries = mutableListOf() - + val entries = mutableListOf() this._evolutionValues[stat]?.let { points -> points.forEach { p -> entries.add(BarEntry(p.x.toFloat(), p.y.toFloat(), p.data)) diff --git a/app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt b/app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt index 7051683f..01b652b1 100644 --- a/app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt +++ b/app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt @@ -1,8 +1,10 @@ package net.pokeranalytics.android.calculus import android.content.Context +import io.realm.RealmModel import net.pokeranalytics.android.R import net.pokeranalytics.android.exceptions.FormattingException +import net.pokeranalytics.android.model.interfaces.Timed import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowViewType import net.pokeranalytics.android.util.CurrencyUtils @@ -11,90 +13,159 @@ import net.pokeranalytics.android.util.extensions.formatted import net.pokeranalytics.android.util.extensions.formattedHourlyDuration import java.util.* +class StatFormattingException(message: String) : Exception(message) { + +} + +interface StatBase : RealmModel { + + fun formattedValue(stat: Stat, context: Context): TextFormat + +} + /** * An enum representing all the types of Session statistics */ -enum class Stat : RowRepresentable { - - NETRESULT, - HOURLY_RATE, - AVERAGE, - NUMBER_OF_SETS, - NUMBER_OF_GAMES, - DURATION, - AVERAGE_DURATION, - NET_BB_PER_100_HANDS, - HOURLY_RATE_BB, - AVERAGE_NET_BB, - WIN_RATIO, - AVERAGE_BUYIN, - ROI, - STANDARD_DEVIATION, - STANDARD_DEVIATION_HOURLY, - STANDARD_DEVIATION_BB_PER_100_HANDS, - HANDS_PLAYED; - - /** - * Returns whether the stat evolution numericValues requires a distribution sorting - */ - fun hasDistributionSorting() : Boolean { - return when (this) { - STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY, STANDARD_DEVIATION_BB_PER_100_HANDS -> true - else -> false - } - } - - companion object { - - fun returnOnInvestment(netResult: Double, buyin: Double) : Double? { - if (buyin == 0.0) { - return null - } - return netResult / buyin - } - - fun netBBPer100Hands(netBB: Double, numberOfHands: Double) : Double? { - if (numberOfHands == 0.0) { - return null - } - return netBB / numberOfHands * 100 - } - - } - - override val resId: Int? - get() { - return when (this) { - NETRESULT -> R.string.net_result - HOURLY_RATE -> R.string.average_hour_rate - AVERAGE -> R.string.average - NUMBER_OF_SETS -> R.string.number_of_sessions - NUMBER_OF_GAMES -> R.string.number_of_records - DURATION -> R.string.duration - AVERAGE_DURATION -> R.string.average_hours_played - NET_BB_PER_100_HANDS -> R.string.net_result_bb_per_100_hands - HOURLY_RATE_BB -> R.string.average_hour_rate_bb_ - AVERAGE_NET_BB -> R.string.average_net_result_bb_ - WIN_RATIO -> R.string.win_ratio - AVERAGE_BUYIN -> R.string.average_buyin - ROI -> R.string.tournament_roi - STANDARD_DEVIATION -> R.string.standard_deviation - STANDARD_DEVIATION_HOURLY -> R.string.standard_deviation_per_hour - STANDARD_DEVIATION_BB_PER_100_HANDS -> R.string.standard_deviation_bb_per_100_hands - HANDS_PLAYED -> R.string.number_of_hands - } - } - - val threshold: Double - get() { - return when (this) { - WIN_RATIO -> 50.0 - else -> 0.0 - } - - } - - override val viewType: Int = RowViewType.TITLE_VALUE.ordinal +enum class Stat(var underlyingClass: Class? = null) : RowRepresentable { + + NETRESULT, + HOURLY_RATE, + AVERAGE, + NUMBER_OF_SETS, + NUMBER_OF_GAMES, + DURATION, + AVERAGE_DURATION, + NET_BB_PER_100_HANDS, + HOURLY_RATE_BB, + AVERAGE_NET_BB, + WIN_RATIO, + AVERAGE_BUYIN, + ROI, + STANDARD_DEVIATION, + STANDARD_DEVIATION_HOURLY, + STANDARD_DEVIATION_BB_PER_100_HANDS, + HANDS_PLAYED; + + /** + * Returns whether the stat evolution numericValues requires a distribution sorting + */ + fun hasDistributionSorting(): Boolean { + return when (this) { + STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY, STANDARD_DEVIATION_BB_PER_100_HANDS -> true + else -> false + } + } + + companion object { + + fun returnOnInvestment(netResult: Double, buyin: Double): Double? { + if (buyin == 0.0) { + return null + } + return netResult / buyin + } + + fun netBBPer100Hands(netBB: Double, numberOfHands: Double): Double? { + if (numberOfHands == 0.0) { + return null + } + return netBB / numberOfHands * 100 + } + + } + + override val resId: Int? + get() { + return when (this) { + NETRESULT -> R.string.net_result + HOURLY_RATE -> R.string.average_hour_rate + AVERAGE -> R.string.average + NUMBER_OF_SETS -> R.string.number_of_sessions + NUMBER_OF_GAMES -> R.string.number_of_records + DURATION -> R.string.duration + AVERAGE_DURATION -> R.string.average_hours_played + NET_BB_PER_100_HANDS -> R.string.net_result_bb_per_100_hands + HOURLY_RATE_BB -> R.string.average_hour_rate_bb_ + AVERAGE_NET_BB -> R.string.average_net_result_bb_ + WIN_RATIO -> R.string.win_ratio + AVERAGE_BUYIN -> R.string.average_buyin + ROI -> R.string.tournament_roi + STANDARD_DEVIATION -> R.string.standard_deviation + STANDARD_DEVIATION_HOURLY -> R.string.standard_deviation_per_hour + STANDARD_DEVIATION_BB_PER_100_HANDS -> R.string.standard_deviation_bb_per_100_hands + HANDS_PLAYED -> R.string.number_of_hands + } + } + + + /** + * Formats the value of the stat to be suitable for display + */ + fun format(value: Double, currency: Currency? = null, context: Context): TextFormat { + + if (value.isNaN()) { + return TextFormat(NULL_TEXT, R.color.white) + } + + when (this) { + // Amounts + red/green + Stat.NETRESULT, Stat.HOURLY_RATE, Stat.AVERAGE -> { + val numberFormat = CurrencyUtils.getCurrencyFormatter(context, currency) + val color = if (value >= this.threshold) R.color.green else R.color.red + return TextFormat(numberFormat.format(value), color) + } + // Red/green numericValues + Stat.HOURLY_RATE_BB, Stat.AVERAGE_NET_BB, Stat.NET_BB_PER_100_HANDS -> { + val color = if (value >= this.threshold) R.color.green else R.color.red + return TextFormat(value.formatted(), color) + } + // white integers + Stat.NUMBER_OF_SETS, Stat.NUMBER_OF_GAMES, Stat.HANDS_PLAYED -> { + return TextFormat("${value.toInt()}") + } // white durations + Stat.DURATION, Stat.AVERAGE_DURATION -> { + return TextFormat(value.formattedHourlyDuration()) + } // red/green percentages + Stat.WIN_RATIO, Stat.ROI -> { + val color = if (value * 100 >= this.threshold) R.color.green else R.color.red + return TextFormat("${(value * 100).formatted()}%", color) + } // white amountsr + Stat.AVERAGE_BUYIN, Stat.STANDARD_DEVIATION, Stat.STANDARD_DEVIATION_HOURLY, + Stat.STANDARD_DEVIATION_BB_PER_100_HANDS -> { + val numberFormat = CurrencyUtils.getCurrencyFormatter(context, currency) + return TextFormat(numberFormat.format(value)) + } + else -> throw FormattingException("Stat formatting of ${this.name} not handled") + } + } + + val threshold: Double + get() { + return when (this) { + WIN_RATIO -> 50.0 + else -> 0.0 + } + + } + + fun cumulativeLabelResId(context: Context) : String { + val resId = when (this) { + AVERAGE, AVERAGE_DURATION, NETRESULT, NET_BB_PER_100_HANDS, + HOURLY_RATE_BB, AVERAGE_NET_BB, ROI, WIN_RATIO, HOURLY_RATE -> R.string.average + NETRESULT, DURATION -> R.string.total + STANDARD_DEVIATION -> R.string.net_result + STANDARD_DEVIATION_HOURLY -> R.string.hour_rate_without_pauses + STANDARD_DEVIATION_BB_PER_100_HANDS -> R.string.net_result_bb_per_100_hands + else -> null + } + resId?.let { + return context.getString(it) + } ?: run { + return NULL_TEXT + } + } + + override val viewType: Int = RowViewType.TITLE_VALUE.ordinal } /** @@ -102,63 +173,29 @@ enum class Stat : RowRepresentable { */ class ComputedStat(var stat: Stat, var value: Double, var currency: Currency? = null) { - constructor(stat: Stat, value: Double, previousValue: Double?) : this(stat, value) { - if (previousValue != null) { - this.variation = (value - previousValue) / previousValue - } - } - - /** - * The variation of the stat - */ - var variation: Double? = null - - /** - * Formats the value of the stat to be suitable for display - */ - fun format(context: Context): TextFormat { - - if (this.value.isNaN()) { - return TextFormat(NULL_TEXT, R.color.white) - } - - when (this.stat) { - // Amounts + red/green - Stat.NETRESULT, Stat.HOURLY_RATE, Stat.AVERAGE -> { - val numberFormat= CurrencyUtils.getCurrencyFormatter(context, currency) - val color = if (this.value >= this.stat.threshold) R.color.green else R.color.red - return TextFormat(numberFormat.format(this.value), color) - } - // Red/green numericValues - Stat.HOURLY_RATE_BB, Stat.AVERAGE_NET_BB, Stat.NET_BB_PER_100_HANDS -> { - val color = if (this.value >= this.stat.threshold) R.color.green else R.color.red - return TextFormat(this.value.formatted(), color) - } - // white integers - Stat.NUMBER_OF_SETS, Stat.NUMBER_OF_GAMES, Stat.HANDS_PLAYED -> { - return TextFormat("${value.toInt()}") - } // white durations - Stat.DURATION, Stat.AVERAGE_DURATION -> { - return TextFormat(value.formattedHourlyDuration()) - } // red/green percentages - Stat.WIN_RATIO, Stat.ROI -> { - val color = if (value * 100 >= this.stat.threshold) R.color.green else R.color.red - return TextFormat("${(value * 100).formatted()}%", color) - } // white amountsr - Stat.AVERAGE_BUYIN, Stat.STANDARD_DEVIATION, Stat.STANDARD_DEVIATION_HOURLY, - Stat.STANDARD_DEVIATION_BB_PER_100_HANDS -> { - val numberFormat= CurrencyUtils.getCurrencyFormatter(context, currency) - return TextFormat(numberFormat.format(this.value)) - } - else -> throw FormattingException("Stat formatting of ${this.stat.name} not handled") - } - } - - /** - * Returns a TextFormat instance for an evolution value located at the specified [index] - */ - fun evolutionValueFormat(index: Int) : TextFormat { - return TextFormat("undef ${index}") - } + constructor(stat: Stat, value: Double, previousValue: Double?) : this(stat, value) { + if (previousValue != null) { + this.variation = (value - previousValue) / previousValue + } + } + + /** + * The variation of the stat + */ + var variation: Double? = null + + /** + * Formats the value of the stat to be suitable for display + */ + fun format(context: Context): TextFormat { + return this.stat.format(this.value, this.currency, context) + } + + /** + * Returns a TextFormat instance for an evolution value located at the specified [index] + */ + fun evolutionValueFormat(index: Int): TextFormat { + return TextFormat("undef ${index}") + } } diff --git a/app/src/main/java/net/pokeranalytics/android/model/interfaces/Timed.kt b/app/src/main/java/net/pokeranalytics/android/model/interfaces/Timed.kt index 0bb7fc80..01431a6e 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/interfaces/Timed.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/interfaces/Timed.kt @@ -1,8 +1,9 @@ package net.pokeranalytics.android.model.interfaces +import net.pokeranalytics.android.calculus.StatBase import java.util.* -interface Timed { +interface Timed : StatBase, Identifiable { fun startDate() : Date? diff --git a/app/src/main/java/net/pokeranalytics/android/model/migrations/PokerAnalyticsMigration.kt b/app/src/main/java/net/pokeranalytics/android/model/migrations/PokerAnalyticsMigration.kt index d27daf72..4588fd95 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/migrations/PokerAnalyticsMigration.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/migrations/PokerAnalyticsMigration.kt @@ -43,7 +43,6 @@ class PokerAnalyticsMigration : RealmMigration { schema.get("SessionSet")?.let { it.addPrimaryKey("id") } - currentVersion++ } diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt index 076649d4..088267ab 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt @@ -13,6 +13,8 @@ import io.realm.kotlin.where import net.pokeranalytics.android.R import net.pokeranalytics.android.calculus.ComputedStat import net.pokeranalytics.android.calculus.Stat +import net.pokeranalytics.android.calculus.StatFormattingException +import net.pokeranalytics.android.calculus.TextFormat import net.pokeranalytics.android.exceptions.ModelException import net.pokeranalytics.android.model.Limit import net.pokeranalytics.android.model.LiveData @@ -365,6 +367,11 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat throw ModelException("Session should have an existing Result relationship") } + val bbHourlyRate: Double + get() { + return this.bbNet / this.hourlyDuration + } + // Manageable override fun isValidForSave(): Boolean { @@ -822,5 +829,45 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat } } + + // Stat Base + + override fun formattedValue(stat: Stat, context: Context) : TextFormat { + + this.result?.let { result -> + + val value: Double? = when (stat) { + Stat.NETRESULT, Stat.AVERAGE, Stat.STANDARD_DEVIATION -> result.net + Stat.NUMBER_OF_GAMES, Stat.NUMBER_OF_SETS -> 1.0 + Stat.AVERAGE_BUYIN -> result.buyin + Stat.ROI -> { + result.buyin?.let { + Stat.returnOnInvestment(result.net, it) + } ?: run { + null + } + } + Stat.HOURLY_RATE_BB -> this.bbHourlyRate + Stat.NET_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION_BB_PER_100_HANDS -> Stat.netBBPer100Hands(this.bbNet, this.estimatedHands) + Stat.AVERAGE_NET_BB -> this.bbNet + Stat.DURATION, Stat.AVERAGE_DURATION -> this.netDuration.toDouble() + Stat.HOURLY_RATE, Stat.STANDARD_DEVIATION_HOURLY -> this.hourlyRate + Stat.HANDS_PLAYED -> this.estimatedHands + else -> throw StatFormattingException("format undefined for stat ${stat.name}") + } + + value?.let { + return stat.format(it, CurrencyUtils.getCurrency(this.bankroll), context) + } ?: run { + return TextFormat(NULL_TEXT) + } + + } ?: run { + throw java.lang.IllegalStateException("Asking for stats on Session without Result") + } + + } + + } diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/SessionSet.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/SessionSet.kt index 6a3ae55d..3a4e36a1 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/SessionSet.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/SessionSet.kt @@ -1,11 +1,15 @@ package net.pokeranalytics.android.model.realm +import android.content.Context import io.realm.Realm import io.realm.RealmObject import io.realm.RealmResults import io.realm.annotations.Ignore import io.realm.annotations.LinkingObjects import io.realm.annotations.PrimaryKey +import net.pokeranalytics.android.calculus.Stat +import net.pokeranalytics.android.calculus.StatFormattingException +import net.pokeranalytics.android.calculus.TextFormat import net.pokeranalytics.android.model.filter.Filterable import net.pokeranalytics.android.model.filter.QueryCondition import net.pokeranalytics.android.model.interfaces.Timed @@ -15,7 +19,7 @@ import java.util.* open class SessionSet() : RealmObject(), Timed, Filterable { @PrimaryKey - var id = UUID.randomUUID().toString() + override var id = UUID.randomUUID().toString() var startDate: Date = Date() set(value) { @@ -74,6 +78,16 @@ open class SessionSet() : RealmObject(), Timed, Filterable { var bbNet: Double = 0.0 + override fun formattedValue(stat: Stat, context: Context) : TextFormat { + return when (stat) { + Stat.NETRESULT, Stat.AVERAGE -> stat.format(this.ratedNet, null, context) + Stat.DURATION, Stat.AVERAGE_DURATION -> stat.format(this.netDuration.toDouble(), null, context) + Stat.HOURLY_RATE -> stat.format(this.hourlyRate, null, context) + Stat.HANDS_PLAYED -> stat.format(this.estimatedHands, null, context) + else -> throw StatFormattingException("format undefined for stat ${stat.name}") + } + } + enum class Field(val identifier: String) { RATED_NET("ratedNet"), HOURLY_RATE("hourlyRate"), diff --git a/app/src/main/java/net/pokeranalytics/android/ui/activity/GraphActivity.kt b/app/src/main/java/net/pokeranalytics/android/ui/activity/GraphActivity.kt index 4140b81a..8f815c9c 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/activity/GraphActivity.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/activity/GraphActivity.kt @@ -9,9 +9,9 @@ import net.pokeranalytics.android.calculus.Stat import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity import net.pokeranalytics.android.ui.fragment.GraphFragment -class GraphParameters(stat: Stat, entries: List) { +class GraphParameters(stat: Stat, entries: List) { var stat: Stat = stat - var entries: List = entries + var entries: List = entries } class GraphActivity : PokerAnalyticsActivity() { diff --git a/app/src/main/java/net/pokeranalytics/android/ui/activity/HomeActivity.kt b/app/src/main/java/net/pokeranalytics/android/ui/activity/HomeActivity.kt index 034d13b3..39602da7 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/activity/HomeActivity.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/activity/HomeActivity.kt @@ -78,7 +78,7 @@ class HomeActivity : PokerAnalyticsActivity() { // observe currency changes this.currencies = realm.where(Currency::class.java).findAll() - this.currencies.addChangeListener { t, set -> + this.currencies.addChangeListener { t, _ -> realm.beginTransaction() t.forEach { diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/GraphFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/GraphFragment.kt index 4f713c6e..a126610a 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/GraphFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/GraphFragment.kt @@ -7,17 +7,20 @@ import android.view.ViewGroup import com.github.mikephil.charting.data.Entry import com.github.mikephil.charting.data.LineData import com.github.mikephil.charting.data.LineDataSet +import com.github.mikephil.charting.highlight.Highlight +import com.github.mikephil.charting.listener.OnChartValueSelectedListener import kotlinx.android.synthetic.main.fragment_graph.* import net.pokeranalytics.android.R import net.pokeranalytics.android.calculus.Stat import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment +import net.pokeranalytics.android.ui.graph.setStyle interface GraphDataSource { } -class GraphFragment : PokerAnalyticsFragment() { +class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener { lateinit var dataSource: GraphDataSource @@ -46,8 +49,48 @@ class GraphFragment : PokerAnalyticsFragment() { private fun initUI() { val dataSet = LineDataSet(this.entries, this.stat.name) + val colors = arrayOf(R.color.green_light).toIntArray() + dataSet.setColors(colors, context) val lineData = LineData(listOf(dataSet)) + + this.chart.setStyle() + this.chart.data = lineData + this.chart.setOnChartValueSelectedListener(this) + + } + + // OnChartValueSelectedListener + + override fun onNothingSelected() { + // nothing to do + } + + override fun onValueSelected(e: Entry?, h: Highlight?) { + + e?.let { entry -> + h?.let { highlight -> + + val id = entry.data as String + val item = getRealm().where(this.stat.underlyingClass).equalTo("id", id).findAll().firstOrNull() + item?.let { + + val date = it.startDate() + + val entryStatName = this.stat.localizedTitle(requireContext()) + val entryValue = it.formattedValue(this.stat, requireContext()) + + val totalStatName = this.stat.cumulativeLabelResId(requireContext()) + val totalStatValue = this.stat.format(e.y.toDouble(), null, requireContext()) + + } + + this.text.text = "" + + + + } + } } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/HistoryFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/HistoryFragment.kt index 6379eae1..78b61b06 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/HistoryFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/HistoryFragment.kt @@ -112,7 +112,7 @@ class HistoryFragment : PokerAnalyticsFragment(), LiveRowRepresentableDataSource private fun initData() { this.realmSessions = getRealm().where().findAll().sort("startDate", Sort.DESCENDING) - this.realmSessions.addChangeListener { t, changeSet -> + this.realmSessions.addChangeListener { _, changeSet -> this.historyAdapter.refreshData() this.historyAdapter.notifyDataSetChanged() } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/graph/GraphExtensions.kt b/app/src/main/java/net/pokeranalytics/android/ui/graph/GraphExtensions.kt new file mode 100644 index 00000000..56cd4dda --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/graph/GraphExtensions.kt @@ -0,0 +1,66 @@ +package net.pokeranalytics.android.ui.graph + +import com.github.mikephil.charting.charts.BarChart +import com.github.mikephil.charting.charts.BarLineChartBase +import com.github.mikephil.charting.charts.LineChart + +fun BarChart.setStyle() { + GraphHelper.setStyle(this) +} + +fun LineChart.setStyle() { + GraphHelper.setStyle(this) +} + +class GraphHelper { + + companion object { + + fun setStyle(chart: BarLineChartBase<*>) { + +// this.xAxis.axisLineColor = ContextCompat.getColor(context, R.color.) ChartAppearance.defaultColor +// this.xAxis.axisLineWidth = ChartAppearance.lineWidth +// this.xAxis.enableGridDashedLine(3.0f, 5.0f, 1.0f) +// +// this.xAxis.labelTextColor = ChartAppearance.defaultColor +// this.xAxis.labelFont = Fonts.graphAxis +// this.xAxis.labelCount = 4 +// this.xAxis.labelPosition = .bottom +// +// this.xAxis.drawLabelsEnabled = true +// this.xAxis.drawGridLinesEnabled = true +// this.xAxis.granularity = 1.0 +// this.xAxis.granularityEnabled = true +// this.xAxis.enabled = true +// +// // Y Axis +// this.leftAxis.drawAxisLineEnabled = false +// this.leftAxis.drawGridLinesEnabled = true +// this.leftAxis.gridLineDashLengths = [3.0, 5.0] +// +// this.leftAxis.drawZeroLineEnabled = true +// this.leftAxis.zeroLineWidth = ChartAppearance.lineWidth +// this.leftAxis.zeroLineColor = ChartAppearance.defaultColor +// +// this.leftAxis.granularityEnabled = true +// this.leftAxis.granularity = 1.0 +// +// this.leftAxis.labelTextColor = ChartAppearance.defaultColor +// this.leftAxis.labelFont = Fonts.graphAxis +// this.leftAxis.labelCount = small ? 1 : 7 // @todo not great if interval is [0..2] for number of records as we get decimals +// +// if timeYAxis { +// this.leftAxis.valueFormatter = HourValueFormatter() +// } else { +// this.leftAxis.valueFormatter = LargeNumberFormatter() +// } +// +// this.rightAxis.enabled = false +// +// this.legend.enabled = false + + } + + } + +} diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/SessionRow.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/SessionRow.kt index 0363c09c..7eb0f035 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/SessionRow.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/rowrepresentable/SessionRow.kt @@ -62,7 +62,6 @@ enum class SessionRow : RowRepresentable { START_DATE, END_DATE, BREAK_TIME, COMMENT ) } - else -> arrayListOf() } } Session.Type.CASH_GAME.ordinal -> { diff --git a/app/src/main/res/layout/fragment_graph.xml b/app/src/main/res/layout/fragment_graph.xml index a24dd14a..cf58c3d3 100644 --- a/app/src/main/res/layout/fragment_graph.xml +++ b/app/src/main/res/layout/fragment_graph.xml @@ -6,9 +6,20 @@ android:layout_width="match_parent" android:layout_height="match_parent"> + + + android:layout_height="match_parent"/> \ No newline at end of file From 8fb052e0b29bb255d31fafcb5c13da41a5804ad1 Mon Sep 17 00:00:00 2001 From: Laurent Date: Wed, 10 Apr 2019 17:26:48 +0200 Subject: [PATCH 2/2] removed useless Filterable use --- .../net/pokeranalytics/android/model/realm/ComputableResult.kt | 2 +- .../main/java/net/pokeranalytics/android/model/realm/Session.kt | 2 +- .../java/net/pokeranalytics/android/model/realm/SessionSet.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/ComputableResult.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/ComputableResult.kt index e0786847..a4c72be8 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/ComputableResult.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/ComputableResult.kt @@ -54,7 +54,7 @@ open class ComputableResult() : RealmObject(), Computable, Filterable { BB_PER100HANDS("bbPer100Hands") } - companion object : Filterable { + companion object { fun fieldNameForQueryType(queryCondition: QueryCondition): String? { return "session." + Session.fieldNameForQueryType(queryCondition) diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt index 088267ab..0cbb2416 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt @@ -50,7 +50,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat TOURNAMENT } - companion object : Filterable { + companion object { fun newInstance(realm: Realm, isTournament: Boolean, bankroll: Bankroll? = null): Session { val session = Session() session.result = Result() diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/SessionSet.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/SessionSet.kt index 3a4e36a1..598d66fb 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/SessionSet.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/SessionSet.kt @@ -96,7 +96,7 @@ open class SessionSet() : RealmObject(), Timed, Filterable { NET_DURATION("netDuration") } - companion object : Filterable { + companion object { fun newInstance(realm: Realm) : SessionSet { val sessionSet = SessionSet()