Merge branch 'dev' of gitlab.com:stax-river/poker-analytics into dev

# Conflicts:
#	app/src/main/java/net/pokeranalytics/android/ui/fragment/GraphFragment.kt
feature/top10
Aurelien Hubert 7 years ago
commit 55f427624c
  1. 44
      app/src/androidTest/java/net/pokeranalytics/android/components/SessionInstrumentedUnitTest.kt
  2. 34
      app/src/androidTest/java/net/pokeranalytics/android/unitTests/StatPerformanceUnitTest.kt
  3. 161
      app/src/androidTest/java/net/pokeranalytics/android/unitTests/StatsInstrumentedUnitTest.kt
  4. 179
      app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt
  5. 34
      app/src/main/java/net/pokeranalytics/android/calculus/Report.kt
  6. 5
      app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt
  7. 10
      app/src/main/java/net/pokeranalytics/android/exceptions/Exceptions.kt
  8. 10
      app/src/main/java/net/pokeranalytics/android/model/comparison/Comparator.kt
  9. 58
      app/src/main/java/net/pokeranalytics/android/model/extensions/SessionExtensions.kt
  10. 17
      app/src/main/java/net/pokeranalytics/android/model/filter/Filterable.kt
  11. 17
      app/src/main/java/net/pokeranalytics/android/model/filter/QueryCondition.kt
  12. 4
      app/src/main/java/net/pokeranalytics/android/model/interfaces/Timed.kt
  13. 12
      app/src/main/java/net/pokeranalytics/android/model/realm/Filter.kt
  14. 281
      app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt
  15. 46
      app/src/main/java/net/pokeranalytics/android/model/realm/SessionSet.kt
  16. 8
      app/src/main/java/net/pokeranalytics/android/ui/activity/HomeActivity.kt
  17. 4
      app/src/main/java/net/pokeranalytics/android/ui/fragment/CalendarFragment.kt
  18. 2
      app/src/main/java/net/pokeranalytics/android/ui/fragment/FilterDetailsFragment.kt
  19. 12
      app/src/main/java/net/pokeranalytics/android/ui/fragment/FiltersFragment.kt
  20. 173
      app/src/main/java/net/pokeranalytics/android/ui/fragment/GraphFragment.kt
  21. 2
      app/src/main/java/net/pokeranalytics/android/ui/fragment/StatsFragment.kt
  22. 10
      app/src/main/java/net/pokeranalytics/android/ui/graph/ChartDataSet.kt
  23. 2
      app/src/main/java/net/pokeranalytics/android/ui/graph/GraphExtensions.kt
  24. 0
      app/src/main/res/layout/fragment_evograph.xml

@ -0,0 +1,44 @@
package net.pokeranalytics.android.components
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.realm.RealmList
import net.pokeranalytics.android.model.realm.*
import org.junit.runner.RunWith
import java.util.*
@RunWith(AndroidJUnit4::class)
open class SessionInstrumentedUnitTest : RealmInstrumentedUnitTest() {
// convenience extension
fun Session.Companion.testInstance(
netResult: Double = 0.0,
isTournament: Boolean = false,
startDate: Date = Date(),
endDate: Int = 1,
bankroll: Bankroll? = null,
game: Game? = null,
location: Location? = null,
tournamentName: TournamentName? = null,
tournamentFeatures: RealmList<TournamentFeature> = RealmList(),
numberOfTable: Int = 1,
limit: Int? = null,
tableSize: Int? = null
): Session {
val session: Session = Session.newInstance(super.mockRealm, isTournament, bankroll)
session.game = game
session.location = location
session.tournamentFeatures = tournamentFeatures
session.tournamentName = tournamentName
session.limit = limit
session.numberOfTables = numberOfTable
session.tableSize = tableSize
session.startDate = startDate
session.result?.netResult = netResult
val cal = Calendar.getInstance() // creates calendar
cal.time = startDate // sets calendar time/date
cal.add(Calendar.HOUR_OF_DAY, endDate) // adds one hour
session.endDate = cal.time // returns new date object, one hour in the future
return session
}
}

@ -0,0 +1,34 @@
package net.pokeranalytics.android.unitTests
import androidx.test.ext.junit.runners.AndroidJUnit4
import net.pokeranalytics.android.components.SessionInstrumentedUnitTest
import net.pokeranalytics.android.model.realm.Result
import net.pokeranalytics.android.model.realm.Session
import org.junit.Test
import org.junit.runner.RunWith
import java.util.*
@RunWith(AndroidJUnit4::class)
class StatPerformanceUnitTest : SessionInstrumentedUnitTest() {
@Test
fun testSessionNetResultOnLoad() {
val realm = mockRealm
realm.beginTransaction()
for (index in 0..100) {
Session.testInstance((-2000..2000).random().toDouble())
println("*** creating $index")
}
realm.commitTransaction()
val d1 = Date()
realm.where(Result::class.java).sum("netResult")
val d2 = Date()
val duration = (d2.time - d1.time)
println("*** ended in $duration milliseconds")
}
}

@ -1,13 +1,13 @@
package net.pokeranalytics.android.unitTests package net.pokeranalytics.android.unitTests
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import io.realm.RealmList
import io.realm.RealmResults import io.realm.RealmResults
import net.pokeranalytics.android.calculus.Calculator import net.pokeranalytics.android.calculus.Calculator
import net.pokeranalytics.android.calculus.ComputableGroup import net.pokeranalytics.android.calculus.ComputableGroup
import net.pokeranalytics.android.calculus.ComputedResults import net.pokeranalytics.android.calculus.ComputedResults
import net.pokeranalytics.android.calculus.Stat import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.components.RealmInstrumentedUnitTest import net.pokeranalytics.android.components.SessionInstrumentedUnitTest
import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.realm.* import net.pokeranalytics.android.model.realm.*
import net.pokeranalytics.android.model.realm.Currency import net.pokeranalytics.android.model.realm.Currency
import org.junit.Assert import org.junit.Assert
@ -23,62 +23,10 @@ import java.util.*
* See [testing documentation](http://d.android.com/tools/testing). * See [testing documentation](http://d.android.com/tools/testing).
*/ */
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() { class StatsInstrumentedUnitTest : SessionInstrumentedUnitTest() {
// convenience extension
private fun Session.Companion.testInstance(
netResult: Double = 0.0,
isTournament: Boolean = false,
startDate: Date = Date(),
endDate: Int = 1,
bankroll: Bankroll? = null,
game: Game? = null,
location: Location? = null,
tournamentName: TournamentName? = null,
tournamentFeatures: RealmList<TournamentFeature> = RealmList(),
numberOfTable: Int = 1,
limit: Int? = null,
tableSize: Int? = null
): Session {
val session: Session = Session.newInstance(super.mockRealm, isTournament, bankroll)
session.game = game
session.location = location
session.tournamentFeatures = tournamentFeatures
session.tournamentName = tournamentName
session.limit = limit
session.numberOfTables = numberOfTable
session.tableSize = tableSize
session.startDate = startDate
session.result?.netResult = netResult
val cal = Calendar.getInstance() // creates calendar
cal.time = startDate // sets calendar time/date
cal.add(Calendar.HOUR_OF_DAY, endDate) // adds one hour
session.endDate = cal.time // returns new date object, one hour in the future
return session
}
@Test
fun testSessionNetResultOnLoad() {
val realm = mockRealm
realm.beginTransaction()
for (index in 0..100) {
Session.testInstance((-2000..2000).random().toDouble())
println("*** creating $index")
}
realm.commitTransaction()
val d1 = Date()
realm.where(Result::class.java).sum("netResult")
val d2 = Date()
val duration = (d2.time - d1.time)
println("*** ended in $duration milliseconds")
}
@Test @Test
fun testSessionStats() { fun testAllSessionStats() {
val realm = this.mockRealm val realm = this.mockRealm
@ -124,8 +72,7 @@ class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() {
println(">>>>>> rated net = ${it.ratedNet} ") println(">>>>>> rated net = ${it.ratedNet} ")
} }
val stats: List<Stat> = listOf(Stat.NETRESULT, Stat.AVERAGE) val group = ComputableGroup("test")
val group = ComputableGroup("test", listOf(), stats)
val options = Calculator.Options() val options = Calculator.Options()
options.displayedStats = listOf(Stat.STANDARD_DEVIATION_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION, options.displayedStats = listOf(Stat.STANDARD_DEVIATION_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION,
@ -510,12 +457,12 @@ class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() {
val sets = realm.where(SessionSet::class.java).findAll() val sets = realm.where(SessionSet::class.java).findAll()
Assert.assertEquals(1, sets.size) assertEquals(1, sets.size)
val set = sets.first() val set = sets.first()
if (set != null) { if (set != null) {
Assert.assertEquals(sd1.time, set.startDate.time) assertEquals(sd1.time, set.startDate.time)
Assert.assertEquals(ed1.time, set.endDate.time) assertEquals(ed1.time, set.endDate.time)
} else { } else {
Assert.fail("No set") Assert.fail("No set")
} }
@ -712,7 +659,6 @@ class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() {
} }
@Test @Test
fun testDaysPlayed() { fun testDaysPlayed() {
@ -748,4 +694,95 @@ class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() {
} }
} }
@Test
fun testFilteredHourlyRate() {
val realm = this.mockRealm
realm.executeTransaction {
val s1 = newSessionInstance(realm, true)
val s2 = newSessionInstance(realm, true)
val s3 = newSessionInstance(realm, false)
val sdf = SimpleDateFormat("dd/M/yyyy hh:mm")
val sd1 = sdf.parse("01/1/2019 09:00")
val ed1 = sdf.parse("01/1/2019 10:00")
val sd2 = sdf.parse("01/1/2019 04:00")
val ed2 = sdf.parse("01/1/2019 05:00")
val sd3 = sdf.parse("01/1/2019 03:00")
val ed3 = sdf.parse("01/1/2019 11:00")
s1.startDate = sd1
s1.endDate = ed1
s2.startDate = sd2
s2.endDate = ed2
s3.startDate = sd3
s3.endDate = ed3
}
val group = ComputableGroup("test", listOf(QueryCondition.CASH))
val options = Calculator.Options()
options.displayedStats = listOf(Stat.DURATION)
val results: ComputedResults = Calculator.compute(realm, group, options)
val delta = 0.01
val duration = results.computedStat(Stat.DURATION)
if (duration != null) {
assertEquals(2.0, duration.value, delta)
} else {
Assert.fail("No Net result stat")
}
}
@Test
fun testFilteredHourlyRate2() {
val realm = this.mockRealm
realm.executeTransaction {
val s1 = newSessionInstance(realm, true)
val s2 = newSessionInstance(realm, true)
val s3 = newSessionInstance(realm, false)
val sdf = SimpleDateFormat("dd/M/yyyy hh:mm")
val sd1 = sdf.parse("01/1/2019 06:00")
val ed1 = sdf.parse("01/1/2019 09:00")
val sd2 = sdf.parse("01/1/2019 07:00")
val ed2 = sdf.parse("01/1/2019 10:00")
val sd3 = sdf.parse("01/1/2019 03:00")
val ed3 = sdf.parse("01/1/2019 11:00")
s1.startDate = sd1
s1.endDate = ed1
s2.startDate = sd2
s2.endDate = ed2
s3.startDate = sd3
s3.endDate = ed3
}
val group = ComputableGroup("test", listOf(QueryCondition.CASH))
val options = Calculator.Options()
options.displayedStats = listOf(Stat.DURATION)
val results: ComputedResults = Calculator.compute(realm, group, options)
val delta = 0.01
val duration = results.computedStat(Stat.DURATION)
if (duration != null) {
assertEquals(4.0, duration.value, delta)
} else {
Assert.fail("No Net result stat")
}
}
} }

@ -4,7 +4,9 @@ import io.realm.Realm
import net.pokeranalytics.android.calculus.Stat.* import net.pokeranalytics.android.calculus.Stat.*
import net.pokeranalytics.android.model.comparison.Comparator import net.pokeranalytics.android.model.comparison.Comparator
import net.pokeranalytics.android.model.comparison.combined import net.pokeranalytics.android.model.comparison.combined
import net.pokeranalytics.android.model.extensions.hourlyDuration
import net.pokeranalytics.android.model.filter.QueryCondition import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.filter.filter
import net.pokeranalytics.android.model.filter.name import net.pokeranalytics.android.model.filter.name
import net.pokeranalytics.android.model.realm.ComputableResult import net.pokeranalytics.android.model.realm.ComputableResult
import net.pokeranalytics.android.model.realm.SessionSet import net.pokeranalytics.android.model.realm.SessionSet
@ -65,9 +67,18 @@ class Calculator {
return false return false
} }
val computeLongestStreak = this.displayedStats.contains(LONGEST_STREAKS) val computeLongestStreak: Boolean
val computeLocationsPlayed = this.displayedStats.contains(LOCATIONS_PLAYED) get() {
val computeDaysPlayed = this.displayedStats.contains(DAYS_PLAYED) return this.displayedStats.contains(LONGEST_STREAKS)
}
val computeLocationsPlayed: Boolean
get() {
return this.displayedStats.contains(LOCATIONS_PLAYED)
}
val computeDaysPlayed: Boolean
get() {
return this.displayedStats.contains(DAYS_PLAYED)
}
} }
@ -137,12 +148,12 @@ class Calculator {
group.cleanup() group.cleanup()
// Computes actual sessionGroup stats // Computes actual sessionGroup stats
val results: ComputedResults = Calculator.compute(realm, group, options = options) val results: ComputedResults = this.compute(realm, group, options = options)
// Computes the compared sessionGroup if existing // Computes the compared sessionGroup if existing
val comparedGroup = group.comparedComputables val comparedGroup = group.comparedComputables
if (comparedGroup != null) { if (comparedGroup != null) {
val comparedResults = Calculator.compute(realm, comparedGroup, options = options) val comparedResults = this.compute(realm, comparedGroup, options = options)
group.comparedComputedResults = comparedResults group.comparedComputedResults = comparedResults
results.computeStatVariations(comparedResults) results.computeStatVariations(comparedResults)
} }
@ -200,7 +211,9 @@ class Calculator {
var tWinningSessionCount = 0 var tWinningSessionCount = 0
var tBuyinSum = 0.0 var tBuyinSum = 0.0
var tHands = 0.0 var tHands = 0.0
var longestWinStreak = 0; var longestLoseStreak = 0; var currentStreak = 0 var longestWinStreak = 0;
var longestLoseStreak = 0;
var currentStreak = 0
computables.forEach { computable -> computables.forEach { computable ->
index++ index++
@ -263,39 +276,49 @@ class Calculator {
val sessionSets = computableGroup.sessionSets(realm) val sessionSets = computableGroup.sessionSets(realm)
// Compute for each serie var gHourlyDuration: Double? = null
val gHourlyDuration = var gBBSum: Double? = null
sessionSets.sum(SessionSet.Field.NET_DURATION.identifier).toDouble() / 3600000 // (milliseconds to hours) var maxDuration: Double? = null
val gSum = sessionSets.sum(SessionSet.Field.RATED_NET.identifier).toDouble()
val gTotalHands = sessionSets.sum(SessionSet.Field.ESTIMATED_HANDS.identifier).toDouble()
val gBBSum = sessionSets.sum(SessionSet.Field.BB_NET.identifier).toDouble()
val maxDuration = sessionSets.max(SessionSet.Field.NET_DURATION.identifier)?.toDouble()
val hourlyRate = gSum / gHourlyDuration if (computableGroup.conditions.size == 0) { // SessionSets are fine
// var bbHourlyRate = gBBSum / gDuration gHourlyDuration =
sessionSets.sum(SessionSet.Field.NET_DURATION.identifier).toDouble() / 3600000 // (milliseconds to hours)
gBBSum = sessionSets.sum(SessionSet.Field.BB_NET.identifier).toDouble()
val shouldIterateOverSets = (options.evolutionValues != Options.EvolutionValues.NONE || options.computeDaysPlayed) sessionSets.max(SessionSet.Field.NET_DURATION.identifier)?.let {
maxDuration = it.toDouble() / 3600000
}
}
val shouldIterateOverSets = computableGroup.conditions.size > 0 ||
options.evolutionValues != Options.EvolutionValues.NONE ||
options.computeDaysPlayed
if (shouldIterateOverSets) { if (shouldIterateOverSets) {
var tHourlyDuration = 0.0 var tHourlyDuration = 0.0
var tIndex = 0 var tIndex = 0
var tSum = 0.0 var tRatedNetSum = 0.0
var tTotalHands = 0.0
var tBBSum = 0.0 var tBBSum = 0.0
var tTotalHands = 0.0
var tHourlyRate = 0.0 var tHourlyRate = 0.0
var tHourlyRateBB = 0.0 var tHourlyRateBB = 0.0
val daysSet = mutableSetOf<Date>() val daysSet = mutableSetOf<Date>()
var tMaxDuration = 0.0
sessionSets.forEach { sessionSet -> sessionSets.forEach { sessionSet ->
tIndex++ tIndex++
tHourlyDuration += sessionSet.hourlyDuration
tSum += sessionSet.ratedNet
tTotalHands += sessionSet.estimatedHands
tBBSum += sessionSet.bbNet
tHourlyRate = gSum / tHourlyDuration val setStats = SSStats(sessionSet, computableGroup.conditions)
tHourlyRateBB = gBBSum / tHourlyDuration
tRatedNetSum += setStats.ratedNet
tBBSum += setStats.bbSum
tHourlyDuration += setStats.hourlyDuration
tTotalHands += setStats.estimatedHands
tMaxDuration = max(tMaxDuration, setStats.hourlyDuration)
tHourlyRate = tRatedNetSum / tHourlyDuration
tHourlyRateBB = tBBSum / tHourlyDuration
daysSet.add(sessionSet.startDate.startOfDay()) daysSet.add(sessionSet.startDate.startOfDay())
when (options.evolutionValues) { when (options.evolutionValues) {
@ -314,13 +337,13 @@ class Calculator {
) )
results.addEvolutionValue(tHourlyRateBB, stat = HOURLY_RATE_BB, data = sessionSet) results.addEvolutionValue(tHourlyRateBB, stat = HOURLY_RATE_BB, data = sessionSet)
Stat.netBBPer100Hands(gBBSum, gTotalHands)?.let { netBB100 -> Stat.netBBPer100Hands(tBBSum, tTotalHands)?.let { netBB100 ->
results.addEvolutionValue(netBB100, stat = NET_BB_PER_100_HANDS, data = sessionSet) results.addEvolutionValue(netBB100, stat = NET_BB_PER_100_HANDS, data = sessionSet)
} }
} }
Options.EvolutionValues.TIMED -> { Options.EvolutionValues.TIMED -> {
results.addEvolutionValue(tSum, tHourlyDuration, NETRESULT, sessionSet) results.addEvolutionValue(tRatedNetSum, tHourlyDuration, NETRESULT, sessionSet)
results.addEvolutionValue(tHourlyRate, tHourlyDuration, HOURLY_RATE, sessionSet) results.addEvolutionValue(tHourlyRate, tHourlyDuration, HOURLY_RATE, sessionSet)
results.addEvolutionValue( results.addEvolutionValue(
tIndex.toDouble(), tIndex.toDouble(),
@ -342,7 +365,7 @@ class Calculator {
) )
results.addEvolutionValue(tHourlyRateBB, tHourlyDuration, HOURLY_RATE_BB, sessionSet) results.addEvolutionValue(tHourlyRateBB, tHourlyDuration, HOURLY_RATE_BB, sessionSet)
Stat.netBBPer100Hands(gBBSum, gTotalHands)?.let { netBB100 -> Stat.netBBPer100Hands(tBBSum, tTotalHands)?.let { netBB100 ->
results.addEvolutionValue( results.addEvolutionValue(
netBB100, netBB100,
tHourlyDuration, tHourlyDuration,
@ -351,14 +374,24 @@ class Calculator {
) )
} }
} }
else -> {
// nothing
}
} }
results.addStat(DAYS_PLAYED, daysSet.size.toDouble()) results.addStat(DAYS_PLAYED, daysSet.size.toDouble())
} }
gHourlyDuration = tHourlyDuration
gBBSum = tBBSum
maxDuration = tMaxDuration
} }
var average = 0.0 var average = 0.0
var hourlyRate = 0.0
if (computables.size > 0) { if (computables.size > 0) {
average = sum / computables.size.toDouble() average = sum / computables.size.toDouble()
val winRatio = winningSessionCount.toDouble() / computables.size.toDouble() val winRatio = winningSessionCount.toDouble() / computables.size.toDouble()
@ -373,25 +406,30 @@ class Calculator {
) )
} }
if (sessionSets.size > 0) { if (gHourlyDuration != null) {
val avgDuration = gHourlyDuration / sessionSets.size
results.addStats( hourlyRate = sum / gHourlyDuration
setOf( if (sessionSets.size > 0) {
ComputedStat(HOURLY_RATE, hourlyRate), val avgDuration = gHourlyDuration / sessionSets.size
ComputedStat(AVERAGE_DURATION, avgDuration) results.addStat(HOURLY_RATE, hourlyRate)
) results.addStat(AVERAGE_DURATION, avgDuration)
) }
results.addStat(DURATION, gHourlyDuration)
}
if (gBBSum != null) {
if (gHourlyDuration != null) {
results.addStat(HOURLY_RATE_BB, gBBSum / gHourlyDuration)
}
results.addStat(AVERAGE_NET_BB, gBBSum / bbSessionCount)
} }
// Create stats // Create stats
results.addStats( results.addStats(
setOf( setOf(
ComputedStat(NETRESULT, sum), ComputedStat(NETRESULT, sum),
ComputedStat(DURATION, gHourlyDuration),
ComputedStat(NUMBER_OF_SETS, sessionSets.size.toDouble()), ComputedStat(NUMBER_OF_SETS, sessionSets.size.toDouble()),
ComputedStat(NUMBER_OF_GAMES, computables.size.toDouble()), ComputedStat(NUMBER_OF_GAMES, computables.size.toDouble()),
ComputedStat(HOURLY_RATE_BB, gBBSum / gHourlyDuration),
ComputedStat(AVERAGE_NET_BB, gBBSum / bbSessionCount),
ComputedStat(HANDS_PLAYED, totalHands) ComputedStat(HANDS_PLAYED, totalHands)
) )
) )
@ -410,7 +448,7 @@ class Calculator {
results.addStat(MINIMUM_NETRESULT, min) results.addStat(MINIMUM_NETRESULT, min)
} }
maxDuration?.let { maxd -> maxDuration?.let { maxd ->
results.addStat(MAXIMUM_DURATION, maxd / 3600000) // (milliseconds to hours) results.addStat(MAXIMUM_DURATION, maxd) // (milliseconds to hours)
} }
val bbPer100Hands = bbSum / totalHands * 100 val bbPer100Hands = bbSum / totalHands * 100
@ -428,20 +466,22 @@ class Calculator {
val standardDeviation = Math.sqrt(stdSum / computables.size) val standardDeviation = Math.sqrt(stdSum / computables.size)
val standardDeviationBBper100Hands = Math.sqrt(stdBBper100HandsSum / computables.size) val standardDeviationBBper100Hands = Math.sqrt(stdBBper100HandsSum / computables.size)
results.addStat(STANDARD_DEVIATION, standardDeviation)
results.addStat(STANDARD_DEVIATION_BB_PER_100_HANDS, standardDeviationBBper100Hands)
// Session Set // Session Set
var hourlyStdSum = 0.0 if (gHourlyDuration != null) {
sessionSets.forEach { set -> var hourlyStdSum = 0.0
hourlyStdSum += Math.pow(set.hourlyRate - hourlyRate, 2.0) sessionSets.forEach { set ->
val ssStats = SSStats(set, computableGroup.conditions)
val sHourlyRate = ssStats.hourlyRate
hourlyStdSum += Math.pow(sHourlyRate - hourlyRate, 2.0)
}
val hourlyStandardDeviation = Math.sqrt(hourlyStdSum / sessionSets.size)
results.addStat(STANDARD_DEVIATION_HOURLY, hourlyStandardDeviation)
} }
val hourlyStandardDeviation = Math.sqrt(hourlyStdSum / sessionSets.size)
results.addStats(
setOf(
ComputedStat(STANDARD_DEVIATION, standardDeviation),
ComputedStat(STANDARD_DEVIATION_HOURLY, hourlyStandardDeviation),
ComputedStat(STANDARD_DEVIATION_BB_PER_100_HANDS, standardDeviationBBper100Hands)
)
)
} }
return results return results
@ -451,3 +491,42 @@ class Calculator {
} }
class SSStats(sessionSet: SessionSet, conditions: List<QueryCondition>) { // Session Set Stats
var hourlyDuration: Double = 0.0
var estimatedHands: Double = 0.0
var bbSum: Double = 0.0
var ratedNet: Double = 0.0
val hourlyRate: Double
get() {
return this.ratedNet / this.hourlyDuration
}
init {
if (sessionSet.sessions?.size == 1) { // use precomputed values
this.initStatsWithSet(sessionSet)
} else { // dynamically filter and compute subset
val setSessions = sessionSet.sessions!!
val filteredSessions = setSessions.filter(conditions)
if (setSessions.size == filteredSessions.size) {
this.initStatsWithSet(sessionSet)
} else {
ratedNet = filteredSessions.sumByDouble { it.computableResult?.ratedNet ?: 0.0 }
bbSum = filteredSessions.sumByDouble { it.bbNet }
hourlyDuration = filteredSessions.hourlyDuration
estimatedHands = filteredSessions.sumByDouble { it.estimatedHands }
}
}
}
private fun initStatsWithSet(sessionSet: SessionSet) {
ratedNet = sessionSet.ratedNet
bbSum = sessionSet.bbNet
hourlyDuration = sessionSet.hourlyDuration
estimatedHands = sessionSet.estimatedHands
}
}

@ -1,5 +1,6 @@
package net.pokeranalytics.android.calculus package net.pokeranalytics.android.calculus
import android.content.Context
import com.github.mikephil.charting.data.BarEntry import com.github.mikephil.charting.data.BarEntry
import com.github.mikephil.charting.data.Entry import com.github.mikephil.charting.data.Entry
import io.realm.Realm import io.realm.Realm
@ -35,13 +36,26 @@ class Report() {
this._results.add(result) this._results.add(result)
} }
fun lineEntries(stat: Stat): List<Entry> {
val entries = mutableListOf<Entry>()
this._results.forEachIndexed { index, results ->
val cs = results.computedStat(stat)
cs?.let { computedStat ->
entries.add(Entry(index.toFloat(), computedStat.value.toFloat(), results))
}
}
return entries
}
fun barEntries(stat: Stat): List<Entry> { fun barEntries(stat: Stat): List<Entry> {
val entries = mutableListOf<Entry>() val entries = mutableListOf<Entry>()
this._results.forEachIndexed { index, results -> this._results.forEachIndexed { index, results ->
val cs = results.computedStat(stat) val cs = results.computedStat(stat)
cs?.let { computedStat -> cs?.let { computedStat ->
entries.add(Entry(index.toFloat(), computedStat.value.toFloat(), results.group)) val barEntry = BarEntry(index.toFloat(), computedStat.value.toFloat(), results)
entries.add(barEntry)
} }
} }
return entries return entries
@ -61,7 +75,7 @@ class Report() {
/** /**
* A sessionGroup of computable items identified by a name * A sessionGroup of computable items identified by a name
*/ */
class ComputableGroup(name: String, conditions: List<QueryCondition>, stats: List<Stat>? = null) { class ComputableGroup(name: String, conditions: List<QueryCondition> = listOf(), stats: List<Stat>? = null) {
/** /**
* The display name of the group * The display name of the group
@ -140,9 +154,10 @@ class ComputableGroup(name: String, conditions: List<QueryCondition>, stats: Lis
get() { get() {
return this._computables?.isEmpty() ?: true return this._computables?.isEmpty() ?: true
} }
} }
class ComputedResults(group: ComputableGroup) { class ComputedResults(group: ComputableGroup) : StatEntry {
/** /**
* The session group used to computed the stats * The session group used to computed the stats
@ -268,6 +283,19 @@ class ComputedResults(group: ComputableGroup) {
val isEmpty: Boolean = this.group.isEmpty val isEmpty: Boolean = this.group.isEmpty
// Stat Entry
override val entryTitle: String = this.group.name
override fun formattedValue(stat: Stat, context: Context): TextFormat {
this.computedStat(stat)?.let {
return it.format(context)
} ?: run {
throw IllegalStateException("Missing stat in results")
}
}
} }
class Point(val x: Double, val y: Double, val data: Any) { class Point(val x: Double, val y: Double, val data: Any) {

@ -3,7 +3,6 @@ package net.pokeranalytics.android.calculus
import android.content.Context import android.content.Context
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.FormattingException import net.pokeranalytics.android.exceptions.FormattingException
import net.pokeranalytics.android.model.interfaces.Identifiable
import net.pokeranalytics.android.model.interfaces.Timed import net.pokeranalytics.android.model.interfaces.Timed
import net.pokeranalytics.android.ui.view.RowRepresentable import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType import net.pokeranalytics.android.ui.view.RowViewType
@ -21,8 +20,10 @@ class ObjectIdentifier(var id: String, var clazz: Class<out Timed>) {
} }
interface StatBase : Identifiable {
interface StatEntry {
val entryTitle: String
fun formattedValue(stat: Stat, context: Context): TextFormat fun formattedValue(stat: Stat, context: Context): TextFormat
} }

@ -11,13 +11,13 @@ class ConfigurationException(message: String) : Exception(message)
sealed class PokerAnalyticsException(message: String) : Exception(message) { sealed class PokerAnalyticsException(message: String) : Exception(message) {
object FilterElementUnknownName : PokerAnalyticsException(message = "No filterElement name was found to identify the queryCondition") object FilterElementUnknownName : PokerAnalyticsException(message = "No filterElement name was found to identify the queryCondition")
object FilterElementUnknownSectionName: PokerAnalyticsException(message = "No filterElement section name was found to identify the queryCondition") object FilterElementUnknownSectionName: PokerAnalyticsException(message = "No filterElement section name was found to identify the queryCondition")
object FilterMissingEntity: PokerAnalyticsException(message = "This filter has no entity initialized") object FilterMissingEntity: PokerAnalyticsException(message = "This queryWith has no entity initialized")
object FilterUnhandledEntity : PokerAnalyticsException(message = "This entity is not filterable") object FilterUnhandledEntity : PokerAnalyticsException(message = "This entity is not filterable")
object QueryValueMapUnknown: PokerAnalyticsException(message = "fieldName is missing") object QueryValueMapUnknown: PokerAnalyticsException(message = "fieldName is missing")
object QueryTypeUnhandled: PokerAnalyticsException(message = "filter type not handled") object QueryTypeUnhandled: PokerAnalyticsException(message = "queryWith type not handled")
object QueryValueMapUnexpectedValue: PokerAnalyticsException(message = "valueMap null not expected") object QueryValueMapUnexpectedValue: PokerAnalyticsException(message = "valueMap null not expected")
object FilterElementExpectedValueMissing : PokerAnalyticsException(message = "filter is empty or null") object FilterElementExpectedValueMissing : PokerAnalyticsException(message = "queryWith is empty or null")
data class FilterElementTypeMissing(val filterElementRow: FilterElementRow) : PokerAnalyticsException(message = "filter element '$filterElementRow' type is missing") data class FilterElementTypeMissing(val filterElementRow: FilterElementRow) : PokerAnalyticsException(message = "queryWith element '$filterElementRow' type is missing")
data class QueryValueMapMissingKeys(val missingKeys: List<String>) : PokerAnalyticsException(message = "valueMap does not contain $missingKeys") data class QueryValueMapMissingKeys(val missingKeys: List<String>) : PokerAnalyticsException(message = "valueMap does not contain $missingKeys")
data class UnknownQueryTypeForRow(val filterElementRow: FilterElementRow) : PokerAnalyticsException(message = "no filter type for $filterElementRow") data class UnknownQueryTypeForRow(val filterElementRow: FilterElementRow) : PokerAnalyticsException(message = "no queryWith type for $filterElementRow")
} }

@ -1,11 +1,9 @@
package net.pokeranalytics.android.model.comparison package net.pokeranalytics.android.model.comparison
import io.realm.Realm import io.realm.Realm
import io.realm.RealmQuery
import io.realm.Sort import io.realm.Sort
import io.realm.kotlin.where import io.realm.kotlin.where
import net.pokeranalytics.android.exceptions.PokerAnalyticsException import net.pokeranalytics.android.exceptions.PokerAnalyticsException
import net.pokeranalytics.android.model.filter.Filterable
import net.pokeranalytics.android.model.filter.QueryCondition import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.realm.* import net.pokeranalytics.android.model.realm.*
import java.util.* import java.util.*
@ -177,14 +175,6 @@ fun List<Comparator>.combined(): List<List<QueryCondition>> {
return getCombinations(comparatorList) return getCombinations(comparatorList)
} }
inline fun <reified T : Filterable> List<QueryCondition>.query(realm: Realm): RealmQuery<T> {
var realmQuery = realm.where<T>()
this.forEach {
realmQuery = it.filter(realmQuery)
}
return realmQuery
}
fun <T> getCombinations(lists: List<List<T>>): List<List<T>> { fun <T> getCombinations(lists: List<List<T>>): List<List<T>> {
var combinations: MutableSet<List<T>> = LinkedHashSet() var combinations: MutableSet<List<T>> = LinkedHashSet()
var newCombinations: MutableSet<List<T>> var newCombinations: MutableSet<List<T>>

@ -25,9 +25,6 @@ enum class SessionState {
*/ */
fun Session.getState(): SessionState { fun Session.getState(): SessionState {
// if (timeFrame == null) {
// return SessionState.PENDING
// }
val start = this.startDate val start = this.startDate
if (start == null) { if (start == null) {
return SessionState.PENDING return SessionState.PENDING
@ -44,3 +41,58 @@ fun Session.getState(): SessionState {
} }
} }
val AbstractList<Session>.hourlyDuration: Double
get() {
val intervals = mutableListOf<TimeInterval>()
this.forEach {
val interval = TimeInterval(it.startDate!!, it.endDate!!, it.breakDuration)
intervals.update(interval)
}
return intervals.sumByDouble { it.hourlyDuration }
}
class TimeInterval(var start: Date, var end: Date, var breakDuration: Long) {
val hourlyDuration: Double
get() {
val netDuration = end.time - start.time - breakDuration
return (netDuration / 3600000).toDouble()
}
}
fun MutableList<TimeInterval>.update(timeInterval: TimeInterval): MutableList<TimeInterval> {
val overlapped = this.filter {
(it.start.before(timeInterval.start) && it.end.after(timeInterval.start)) ||
(it.start.before(timeInterval.end) && it.end.after(timeInterval.end)) ||
(it.start.after(timeInterval.start) && it.end.before(timeInterval.end))
}
if (overlapped.size == 0) { // add
this.add(timeInterval)
} else { // update
var start = timeInterval.start
var end = timeInterval.end
var breakDuration = timeInterval.breakDuration
overlapped.forEach {
if (it.start.before(start)) {
start = it.start
}
if (it.end.after(end)) {
end = it.end
}
breakDuration = kotlin.math.max(it.breakDuration, breakDuration)
}
this.removeAll(overlapped)
this.add(TimeInterval(start, end, breakDuration))
}
return this
}

@ -1,6 +1,7 @@
package net.pokeranalytics.android.model.filter package net.pokeranalytics.android.model.filter
import io.realm.RealmModel import io.realm.RealmModel
import io.realm.RealmResults
import net.pokeranalytics.android.model.realm.ComputableResult import net.pokeranalytics.android.model.realm.ComputableResult
import net.pokeranalytics.android.model.realm.Session import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.model.realm.SessionSet import net.pokeranalytics.android.model.realm.SessionSet
@ -11,7 +12,7 @@ import net.pokeranalytics.android.model.realm.SessionSet
* - filters can be applied to different type of objects: Sessions, Hands, Transactions... * - filters can be applied to different type of objects: Sessions, Hands, Transactions...
* - filters can be applied to a list of different type of objects (feed) * - filters can be applied to a list of different type of objects (feed)
* *
* A filter is described by the following: * A queryWith is described by the following:
* - a data type: Session, Hands... * - a data type: Session, Hands...
* - a field: table size of a Session * - a field: table size of a Session
* - an operator: equal, >=, <... * - an operator: equal, >=, <...
@ -27,7 +28,7 @@ import net.pokeranalytics.android.model.realm.SessionSet
* - multiple numericValues as 'OR' * - multiple numericValues as 'OR'
* *
* Also: * Also:
* A filter should be able to be converted into a Realm query * A queryWith should be able to be converted into a Realm query
* *
*/ */
@ -47,6 +48,10 @@ interface Filterable : RealmModel {
} }
inline fun <reified T : Filterable> RealmResults<T>.filter(conditions: List<QueryCondition>) : RealmResults<T> {
return conditions.queryWith(this.where()).findAll()
}
class FilterHelper { class FilterHelper {
companion object { companion object {
@ -68,11 +73,13 @@ class FilterHelper {
} }
// //
//fun MutableList<Filterable>.filter(filter: FilterCondition) : List<Filterable> { //fun MutableList<Filterable>.queryWith(queryWith: FilterCondition) : List<Filterable> {
// //
// return this.filter { f -> // return this.queryWith { f ->
// return@filter true // return@queryWith true
// } // }
//} //}

@ -1,10 +1,7 @@
package net.pokeranalytics.android.model.filter package net.pokeranalytics.android.model.filter
import io.realm.RealmQuery import io.realm.RealmQuery
import io.realm.internal.Table
import net.pokeranalytics.android.exceptions.PokerAnalyticsException import net.pokeranalytics.android.exceptions.PokerAnalyticsException
import net.pokeranalytics.android.model.Limit
import net.pokeranalytics.android.model.TableSize
import net.pokeranalytics.android.model.realm.FilterCondition import net.pokeranalytics.android.model.realm.FilterCondition
import net.pokeranalytics.android.model.realm.Session import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.util.extensions.endOfDay import net.pokeranalytics.android.util.extensions.endOfDay
@ -15,6 +12,18 @@ fun List<QueryCondition>.name() : String {
return this.map { it.name }.joinToString(" / ") return this.map { it.name }.joinToString(" / ")
} }
//inline fun <reified T : Filterable> List<QueryCondition>.query(realm: Realm): RealmQuery<T> {
// return this.queryWith(realm.where())
//}
inline fun <reified T : Filterable> List<QueryCondition>.queryWith(query: RealmQuery<T>): RealmQuery<T> {
var realmQuery = query
this.forEach {
realmQuery = it.queryWith(realmQuery)
}
return realmQuery
}
/** /**
* Enum describing the way a query should be handled * Enum describing the way a query should be handled
* Some queries requires a value to be checked upon through equals, in, more, less, between * Some queries requires a value to be checked upon through equals, in, more, less, between
@ -194,7 +203,7 @@ sealed class QueryCondition(var operator: Operator? = null) {
* main method of the enum * main method of the enum
* providing a base RealmQuery [realmQuery], the method is able to attached the corresponding query and returns the newly formed [RealmQuery] * providing a base RealmQuery [realmQuery], the method is able to attached the corresponding query and returns the newly formed [RealmQuery]
*/ */
inline fun <reified T : Filterable> filter(realmQuery: RealmQuery<T>): RealmQuery<T> { inline fun <reified T : Filterable> queryWith(realmQuery: RealmQuery<T>): RealmQuery<T> {
val fieldName = FilterHelper.fieldNameForQueryType<T>(this) val fieldName = FilterHelper.fieldNameForQueryType<T>(this)
fieldName ?: throw PokerAnalyticsException.QueryValueMapUnknown fieldName ?: throw PokerAnalyticsException.QueryValueMapUnknown
when (operator) { when (operator) {

@ -1,10 +1,10 @@
package net.pokeranalytics.android.model.interfaces package net.pokeranalytics.android.model.interfaces
import net.pokeranalytics.android.calculus.ObjectIdentifier import net.pokeranalytics.android.calculus.ObjectIdentifier
import net.pokeranalytics.android.calculus.StatBase import net.pokeranalytics.android.calculus.StatEntry
import java.util.* import java.util.*
interface Timed : StatBase { interface Timed : StatEntry, Identifiable {
fun startDate() : Date? fun startDate() : Date?

@ -35,7 +35,7 @@ open class Filter : RealmObject() {
return realm.copyToRealm(filter) return realm.copyToRealm(filter)
} }
// Get a filter by its id // Get a queryWith by its id
fun getFilterBydId(realm: Realm, filterId: String): Filter? { fun getFilterBydId(realm: Realm, filterId: String): Filter? {
return realm.where<Filter>().equalTo("id", filterId).findFirst() return realm.where<Filter>().equalTo("id", filterId).findFirst()
} }
@ -44,7 +44,7 @@ open class Filter : RealmObject() {
inline fun <reified T : Filterable> queryOn(realm: Realm, queries: List<QueryCondition>): RealmResults<T> { inline fun <reified T : Filterable> queryOn(realm: Realm, queries: List<QueryCondition>): RealmResults<T> {
var realmQuery = realm.where<T>() var realmQuery = realm.where<T>()
queries.forEach { queries.forEach {
realmQuery = it.filter(realmQuery) realmQuery = it.queryWith(realmQuery)
} }
Timber.d(">>> Filter query: ${realmQuery.description}") Timber.d(">>> Filter query: ${realmQuery.description}")
return realmQuery.findAll() return realmQuery.findAll()
@ -54,10 +54,10 @@ open class Filter : RealmObject() {
@PrimaryKey @PrimaryKey
var id = UUID.randomUUID().toString() var id = UUID.randomUUID().toString()
// the filter name // the queryWith name
var name: String = "" var name: String = ""
// the number of use of the filter, // the number of use of the queryWith,
// for MutableRealmInteger, see https://realm.io/docs/java/latest/#counters // for MutableRealmInteger, see https://realm.io/docs/java/latest/#counters
val usageCount: MutableRealmInteger = MutableRealmInteger.valueOf(0) val usageCount: MutableRealmInteger = MutableRealmInteger.valueOf(0)
@ -106,7 +106,7 @@ open class Filter : RealmObject() {
} }
/** /**
* Set the saved value in the filter for the given [filterElementRow] * Set the saved value in the queryWith for the given [filterElementRow]
*/ */
fun setSavedValueForElement(filterElementRow: FilterElementRow) { fun setSavedValueForElement(filterElementRow: FilterElementRow) {
when (filterElementRow) { when (filterElementRow) {
@ -140,7 +140,7 @@ open class Filter : RealmObject() {
this.filterConditions.map { this.filterConditions.map {
it.queryCondition it.queryCondition
}.forEach { }.forEach {
realmQuery = it.filter(realmQuery) realmQuery = it.queryWith(realmQuery)
} }
return realmQuery.findAll() return realmQuery.findAll()

@ -35,6 +35,7 @@ import net.pokeranalytics.android.ui.view.rowrepresentable.SessionRow
import net.pokeranalytics.android.util.NULL_TEXT import net.pokeranalytics.android.util.NULL_TEXT
import net.pokeranalytics.android.util.UserDefaults import net.pokeranalytics.android.util.UserDefaults
import net.pokeranalytics.android.util.extensions.* import net.pokeranalytics.android.util.extensions.*
import java.text.DateFormat
import java.util.* import java.util.*
import java.util.Currency import java.util.Currency
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
@ -42,7 +43,7 @@ import kotlin.collections.ArrayList
typealias BB = Double typealias BB = Double
open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDataSource, RowRepresentable, Timed, open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDataSource, RowRepresentable, Timed,
TimeFilterable, Filterable { TimeFilterable, Filterable {
enum class Type { enum class Type {
CASH_GAME, CASH_GAME,
@ -67,28 +68,28 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
LIVE, ONLINE -> "bankroll.live" LIVE, ONLINE -> "bankroll.live"
CASH, TOURNAMENT -> "type" CASH, TOURNAMENT -> "type"
is BANKROLL -> "bankroll.id" is BANKROLL -> "bankroll.id"
is GAME -> "game.id" is GAME -> "game.id"
is TOURNAMENT_NAME -> "tournamentName.id" is TOURNAMENT_NAME -> "tournamentName.id"
is ANY_TOURNAMENT_FEATURES, is ALL_TOURNAMENT_FEATURES -> "tournamentFeatures.id" is ANY_TOURNAMENT_FEATURES, is ALL_TOURNAMENT_FEATURES -> "tournamentFeatures.id"
is LOCATION -> "location.id" is LOCATION -> "location.id"
is LIMIT -> "limit" is LIMIT -> "limit"
is TABLE_SIZE -> "tableSize" is TABLE_SIZE -> "tableSize"
is TOURNAMENT_TYPE -> "tournamentType" is TOURNAMENT_TYPE -> "tournamentType"
is BLIND -> "blinds" is BLIND -> "blinds"
is COMMENT -> "comment" is COMMENT -> "comment"
is BETWEEN_NUMBER_OF_TABLE, is MORE_NUMBER_OF_TABLE, is LESS_NUMBER_OF_TABLE -> "numberOfTable" is BETWEEN_NUMBER_OF_TABLE, is MORE_NUMBER_OF_TABLE, is LESS_NUMBER_OF_TABLE -> "numberOfTable"
is MORE_THAN_NET_RESULT, is LESS_THAN_NET_RESULT -> "computableResults.ratedNet" is MORE_THAN_NET_RESULT, is LESS_THAN_NET_RESULT -> "computableResults.ratedNet"
is MORE_THAN_BUY_IN, is LESS_THAN_BUY_IN -> "result.buyin" is MORE_THAN_BUY_IN, is LESS_THAN_BUY_IN -> "result.buyin"
is MORE_THAN_CASH_OUT, is LESS_THAN_CASH_OUT -> "result.cashout" is MORE_THAN_CASH_OUT, is LESS_THAN_CASH_OUT -> "result.cashout"
is MORE_THAN_TIPS, is LESS_THAN_TIPS -> "result.tips" is MORE_THAN_TIPS, is LESS_THAN_TIPS -> "result.tips"
is MORE_THAN_NUMBER_OF_PLAYER, is LESS_THAN_NUMBER_OF_PLAYER, is BETWEEN_NUMBER_OF_PLAYER -> "tournamentNumberOfPlayers" is MORE_THAN_NUMBER_OF_PLAYER, is LESS_THAN_NUMBER_OF_PLAYER, is BETWEEN_NUMBER_OF_PLAYER -> "tournamentNumberOfPlayers"
is MORE_THAN_TOURNAMENT_FEE, is LESS_THAN_TOURNAMENT_FEE, is BETWEEN_TOURNAMENT_FEE -> "tournamentEntryFee" is MORE_THAN_TOURNAMENT_FEE, is LESS_THAN_TOURNAMENT_FEE, is BETWEEN_TOURNAMENT_FEE -> "tournamentEntryFee"
is STARTED_FROM_DATE, is STARTED_TO_DATE -> "startDate" is STARTED_FROM_DATE, is STARTED_TO_DATE -> "startDate"
is ENDED_FROM_DATE, is ENDED_TO_DATE -> "endDate" is ENDED_FROM_DATE, is ENDED_TO_DATE -> "endDate"
is DAY_OF_WEEK, is WEEK_END, is WEEK_DAY -> "dayOfWeek" is DAY_OF_WEEK, is WEEK_END, is WEEK_DAY -> "dayOfWeek"
is MONTH -> "month" is MONTH -> "month"
is YEAR -> "year" is YEAR -> "year"
TODAY, YESTERDAY, TODAY_AND_YESTERDAY, THIS_YEAR, THIS_MONTH, THIS_WEEK -> "startDate" TODAY, YESTERDAY, TODAY_AND_YESTERDAY, THIS_YEAR, THIS_MONTH, THIS_WEEK -> "startDate"
else -> null else -> null
} }
} }
@ -114,7 +115,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
// Timed interface // Timed interface
override var dayOfWeek : Int? = null override var dayOfWeek: Int? = null
override var month: Int? = null override var month: Int? = null
override var year: Int? = null override var year: Int? = null
override var dayOfMonth: Int? = null override var dayOfMonth: Int? = null
@ -213,21 +214,21 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
// The small blind value // The small blind value
var cgSmallBlind: Double? = null var cgSmallBlind: Double? = null
set(value) { set(value) {
field = value field = value
formatBlinds() formatBlinds()
} }
// The big blind value // The big blind value
var cgBigBlind: Double? = null var cgBigBlind: Double? = null
set(value) { set(value) {
field = value field = value
this.computeStats() this.computeStats()
formatBlinds() formatBlinds()
} }
var blinds: String? = null var blinds: String? = null
private set private set
// Tournament // Tournament
@ -246,9 +247,9 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
// The features of the tournament, like Knockout, Shootout, Turbo... // The features of the tournament, like Knockout, Shootout, Turbo...
var tournamentFeatures: RealmList<TournamentFeature> = RealmList() var tournamentFeatures: RealmList<TournamentFeature> = RealmList()
fun bankrollHasBeenUpdated() { fun bankrollHasBeenUpdated() {
formatBlinds() formatBlinds()
} }
/** /**
* Manages impacts on SessionSets * Manages impacts on SessionSets
@ -303,7 +304,8 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
*/ */
val bbNet: BB val bbNet: BB
get() { get() {
val bb = this.cgBigBlind; val result = this.result val bb = this.cgBigBlind;
val result = this.result
if (bb != null && result != null) { if (bb != null && result != null) {
return result.net / bb return result.net / bb
} else { } else {
@ -487,19 +489,19 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
return NULL_TEXT return NULL_TEXT
} }
val hasDefaultCurrency: Boolean val hasDefaultCurrency: Boolean
get() { get() {
return bankroll?.currency?.code == null return bankroll?.currency?.code == null
} }
val currency : Currency val currency: Currency
get() { get() {
return bankroll?.currency?.code?.let { return bankroll?.currency?.code?.let {
Currency.getInstance(it) Currency.getInstance(it)
} ?: run { } ?: run {
UserDefaults.currency UserDefaults.currency
} }
} }
/** /**
* Return the game title * Return the game title
@ -518,18 +520,18 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
return if (gameTitle.isNotBlank()) gameTitle else NULL_TEXT return if (gameTitle.isNotBlank()) gameTitle else NULL_TEXT
} }
fun getFormattedBlinds(): String { fun getFormattedBlinds(): String {
return blinds ?: NULL_TEXT return blinds ?: NULL_TEXT
} }
private fun formatBlinds() { private fun formatBlinds() {
blinds = null blinds = null
if (cgBigBlind == null) return if (cgBigBlind == null) return
cgBigBlind?.let { bb -> cgBigBlind?.let { bb ->
val sb = cgSmallBlind ?: bb / 2.0 val sb = cgSmallBlind ?: bb / 2.0
blinds = "${currency.symbol} ${sb.formatted()}/${bb.round()}" blinds = "${currency.symbol} ${sb.formatted()}/${bb.round()}"
} }
} }
// LifeCycle // LifeCycle
@ -569,7 +571,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
} }
@Ignore @Ignore
private var rowRepresentationForCurrentState : List<RowRepresentable> = mutableListOf() private var rowRepresentationForCurrentState: List<RowRepresentable> = mutableListOf()
private fun updatedRowRepresentationForCurrentState(): List<RowRepresentable> { private fun updatedRowRepresentationForCurrentState(): List<RowRepresentable> {
val rows = ArrayList<RowRepresentable>() val rows = ArrayList<RowRepresentable>()
@ -669,7 +671,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
} }
SessionRow.TOURNAMENT_FEATURE -> { SessionRow.TOURNAMENT_FEATURE -> {
if (tournamentFeatures.size > 2) { if (tournamentFeatures.size > 2) {
"${tournamentFeatures.subList(0,2).joinToString { "${tournamentFeatures.subList(0, 2).joinToString {
it.name it.name
}}, ..." }}, ..."
} else if (tournamentFeatures.size > 0) { } else if (tournamentFeatures.size > 0) {
@ -696,56 +698,98 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
override fun editDescriptors(row: RowRepresentable): ArrayList<RowRepresentableEditDescriptor>? { override fun editDescriptors(row: RowRepresentable): ArrayList<RowRepresentableEditDescriptor>? {
return when (row) { return when (row) {
SessionRow.BANKROLL -> row.editingDescriptors(mapOf( SessionRow.BANKROLL -> row.editingDescriptors(
"defaultValue" to this.bankroll, mapOf(
"data" to LiveData.BANKROLL.items(realm))) "defaultValue" to this.bankroll,
SessionRow.GAME -> row.editingDescriptors(mapOf( "data" to LiveData.BANKROLL.items(realm)
"limit" to this.limit, )
"defaultValue" to this.game, )
"data" to LiveData.GAME.items(realm))) SessionRow.GAME -> row.editingDescriptors(
SessionRow.LOCATION -> row.editingDescriptors(mapOf( mapOf(
"defaultValue" to this.location, "limit" to this.limit,
"data" to LiveData.LOCATION.items(realm))) "defaultValue" to this.game,
SessionRow.TOURNAMENT_FEATURE -> row.editingDescriptors(mapOf( "data" to LiveData.GAME.items(realm)
"defaultValue" to this.tournamentFeatures, )
"data" to LiveData.TOURNAMENT_FEATURE.items(realm))) )
SessionRow.TOURNAMENT_NAME -> row.editingDescriptors(mapOf( SessionRow.LOCATION -> row.editingDescriptors(
"defaultValue" to this.tournamentName, mapOf(
"data" to LiveData.TOURNAMENT_NAME.items(realm))) "defaultValue" to this.location,
SessionRow.TOURNAMENT_TYPE -> row.editingDescriptors(mapOf( "data" to LiveData.LOCATION.items(realm)
"defaultValue" to this.tournamentType)) )
SessionRow.TABLE_SIZE -> row.editingDescriptors(mapOf( )
"defaultValue" to this.tableSize)) SessionRow.TOURNAMENT_FEATURE -> row.editingDescriptors(
SessionRow.BLINDS -> row.editingDescriptors(mapOf( mapOf(
"sb" to cgSmallBlind?.round(), "defaultValue" to this.tournamentFeatures,
"bb" to cgBigBlind?.round() "data" to LiveData.TOURNAMENT_FEATURE.items(realm)
)) )
SessionRow.BUY_IN -> row.editingDescriptors(mapOf( )
"bb" to cgBigBlind, SessionRow.TOURNAMENT_NAME -> row.editingDescriptors(
"fee" to this.tournamentEntryFee, mapOf(
"ratedBuyin" to result?.buyin "defaultValue" to this.tournamentName,
)) "data" to LiveData.TOURNAMENT_NAME.items(realm)
)
)
SessionRow.TOURNAMENT_TYPE -> row.editingDescriptors(
mapOf(
"defaultValue" to this.tournamentType
)
)
SessionRow.TABLE_SIZE -> row.editingDescriptors(
mapOf(
"defaultValue" to this.tableSize
)
)
SessionRow.BLINDS -> row.editingDescriptors(
mapOf(
"sb" to cgSmallBlind?.round(),
"bb" to cgBigBlind?.round()
)
)
SessionRow.BUY_IN -> row.editingDescriptors(
mapOf(
"bb" to cgBigBlind,
"fee" to this.tournamentEntryFee,
"ratedBuyin" to result?.buyin
)
)
SessionRow.BREAK_TIME -> row.editingDescriptors(mapOf()) SessionRow.BREAK_TIME -> row.editingDescriptors(mapOf())
SessionRow.CASHED_OUT, SessionRow.PRIZE -> row.editingDescriptors(mapOf( SessionRow.CASHED_OUT, SessionRow.PRIZE -> row.editingDescriptors(
"defaultValue" to result?.cashout mapOf(
)) "defaultValue" to result?.cashout
SessionRow.NET_RESULT -> row.editingDescriptors(mapOf( )
"defaultValue" to result?.netResult )
)) SessionRow.NET_RESULT -> row.editingDescriptors(
SessionRow.COMMENT -> row.editingDescriptors(mapOf( mapOf(
"defaultValue" to this.comment)) "defaultValue" to result?.netResult
SessionRow.INITIAL_BUY_IN -> row.editingDescriptors(mapOf( )
"defaultValue" to this.tournamentEntryFee )
)) SessionRow.COMMENT -> row.editingDescriptors(
SessionRow.PLAYERS -> row.editingDescriptors(mapOf( mapOf(
"defaultValue" to this.tournamentNumberOfPlayers)) "defaultValue" to this.comment
SessionRow.POSITION -> row.editingDescriptors(mapOf( )
"defaultValue" to this.result?.tournamentFinalPosition)) )
SessionRow.TIPS -> row.editingDescriptors(mapOf( SessionRow.INITIAL_BUY_IN -> row.editingDescriptors(
"sb" to cgSmallBlind?.round(), mapOf(
"bb" to cgBigBlind?.round(), "defaultValue" to this.tournamentEntryFee
"tips" to result?.tips )
)) )
SessionRow.PLAYERS -> row.editingDescriptors(
mapOf(
"defaultValue" to this.tournamentNumberOfPlayers
)
)
SessionRow.POSITION -> row.editingDescriptors(
mapOf(
"defaultValue" to this.result?.tournamentFinalPosition
)
)
SessionRow.TIPS -> row.editingDescriptors(
mapOf(
"sb" to cgSmallBlind?.round(),
"bb" to cgBigBlind?.round(),
"tips" to result?.tips
)
)
else -> null else -> null
} }
} }
@ -782,13 +826,15 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
this.breakDuration = (value as Double? ?: 0.0).toLong() * 60 * 1000 this.breakDuration = (value as Double? ?: 0.0).toLong() * 60 * 1000
} }
SessionRow.BUY_IN -> { SessionRow.BUY_IN -> {
val localResult = if (this.result != null) this.result as Result else realm.createObject(Result::class.java) val localResult =
if (this.result != null) this.result as Result else realm.createObject(Result::class.java)
localResult.buyin = value as Double? localResult.buyin = value as Double?
this.result = localResult this.result = localResult
this.updateRowRepresentation() this.updateRowRepresentation()
} }
SessionRow.CASHED_OUT, SessionRow.PRIZE -> { SessionRow.CASHED_OUT, SessionRow.PRIZE -> {
val localResult = if (this.result != null) this.result as Result else realm.createObject(Result::class.java) val localResult =
if (this.result != null) this.result as Result else realm.createObject(Result::class.java)
localResult.cashout = value as Double? localResult.cashout = value as Double?
@ -870,7 +916,12 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
// Stat Base // Stat Base
override fun formattedValue(stat: Stat, context: Context) : TextFormat { override val entryTitle: String
get() {
return DateFormat.getDateInstance(DateFormat.SHORT).format(this.startDate)
}
override fun formattedValue(stat: Stat, context: Context): TextFormat {
this.result?.let { result -> this.result?.let { result ->
@ -886,7 +937,10 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
} }
} }
Stat.HOURLY_RATE_BB -> this.bbHourlyRate 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.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.AVERAGE_NET_BB -> this.bbNet
Stat.DURATION, Stat.AVERAGE_DURATION -> this.netDuration.toDouble() Stat.DURATION, Stat.AVERAGE_DURATION -> this.netDuration.toDouble()
Stat.HOURLY_RATE, Stat.STANDARD_DEVIATION_HOURLY -> this.hourlyRate Stat.HOURLY_RATE, Stat.STANDARD_DEVIATION_HOURLY -> this.hourlyRate
@ -914,4 +968,3 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
} }

@ -15,6 +15,7 @@ import net.pokeranalytics.android.model.filter.Filterable
import net.pokeranalytics.android.model.filter.QueryCondition import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.interfaces.Timed import net.pokeranalytics.android.model.interfaces.Timed
import net.pokeranalytics.android.util.NULL_TEXT import net.pokeranalytics.android.util.NULL_TEXT
import java.text.DateFormat
import java.util.* import java.util.*
@ -86,25 +87,6 @@ open class SessionSet() : RealmObject(), Timed, Filterable {
return this.bbNet / this.hourlyDuration return this.bbNet / this.hourlyDuration
} }
override fun formattedValue(stat: Stat, context: Context) : TextFormat {
return when (stat) {
Stat.NETRESULT, Stat.AVERAGE -> stat.format(this.ratedNet, currency = null, context = context)
Stat.DURATION, Stat.AVERAGE_DURATION -> stat.format(this.netDuration.toDouble(), currency = null, context = context)
Stat.HOURLY_RATE -> stat.format(this.hourlyRate, currency = null, context = context)
Stat.HANDS_PLAYED -> stat.format(this.estimatedHands, currency = null, context = context)
Stat.HOURLY_RATE_BB -> stat.format(this.bbHourlyRate, currency = null, context = context)
Stat.NET_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION_BB_PER_100_HANDS -> {
val netBBPer100Hands = Stat.netBBPer100Hands(this.bbNet, this.estimatedHands)
if (netBBPer100Hands != null) {
return stat.format(this.estimatedHands, currency = null, context = context)
} else {
return TextFormat(NULL_TEXT)
}
}
else -> throw StatFormattingException("format undefined for stat ${stat.name}")
}
}
enum class Field(val identifier: String) { enum class Field(val identifier: String) {
RATED_NET("ratedNet"), RATED_NET("ratedNet"),
HOURLY_RATE("hourlyRate"), HOURLY_RATE("hourlyRate"),
@ -126,6 +108,32 @@ open class SessionSet() : RealmObject(), Timed, Filterable {
} }
// Stat Base
override val entryTitle: String
get() {
return DateFormat.getDateInstance(DateFormat.SHORT).format(this.startDate)
}
override fun formattedValue(stat: Stat, context: Context) : TextFormat {
return when (stat) {
Stat.NETRESULT, Stat.AVERAGE -> stat.format(this.ratedNet, currency = null, context = context)
Stat.DURATION, Stat.AVERAGE_DURATION -> stat.format(this.netDuration.toDouble(), currency = null, context = context)
Stat.HOURLY_RATE -> stat.format(this.hourlyRate, currency = null, context = context)
Stat.HANDS_PLAYED -> stat.format(this.estimatedHands, currency = null, context = context)
Stat.HOURLY_RATE_BB -> stat.format(this.bbHourlyRate, currency = null, context = context)
Stat.NET_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION_BB_PER_100_HANDS -> {
val netBBPer100Hands = Stat.netBBPer100Hands(this.bbNet, this.estimatedHands)
if (netBBPer100Hands != null) {
return stat.format(this.estimatedHands, currency = null, context = context)
} else {
return TextFormat(NULL_TEXT)
}
}
else -> throw StatFormattingException("format undefined for stat ${stat.name}")
}
}
// Timed // Timed
override val objectIdentifier: ObjectIdentifier override val objectIdentifier: ObjectIdentifier

@ -75,7 +75,7 @@ class HomeActivity : PokerAnalyticsActivity() {
override fun onCreateOptionsMenu(menu: Menu?): Boolean { override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.toolbar_home, menu) menuInflater.inflate(R.menu.toolbar_home, menu)
this.homeMenu = menu this.homeMenu = menu
//TODO: Change filter button visibility //TODO: Change queryWith button visibility
homeMenu?.findItem(R.id.filter)?.isVisible = true homeMenu?.findItem(R.id.filter)?.isVisible = true
return super.onCreateOptionsMenu(menu) return super.onCreateOptionsMenu(menu)
} }
@ -153,15 +153,15 @@ class HomeActivity : PokerAnalyticsActivity() {
/* /*
0 -> { 0 -> {
toolbar.title = getString(R.string.feed) toolbar.title = getString(R.string.feed)
homeMenu?.findItem(R.id.filter)?.isVisible = false homeMenu?.findItem(R.id.queryWith)?.isVisible = false
} }
1 -> { 1 -> {
toolbar.title = getString(R.string.stats) toolbar.title = getString(R.string.stats)
homeMenu?.findItem(R.id.filter)?.isVisible = false homeMenu?.findItem(R.id.queryWith)?.isVisible = false
} }
2 -> { 2 -> {
toolbar.title = getString(R.string.services) toolbar.title = getString(R.string.services)
homeMenu?.findItem(R.id.filter)?.isVisible = false homeMenu?.findItem(R.id.queryWith)?.isVisible = false
} }
*/ */

@ -151,7 +151,7 @@ class CalendarFragment : SessionObserverFragment(), CoroutineScope, StaticRowRep
} }
}) })
// Manage session type filter // Manage session type queryWith
filterSessionAll.setOnCheckedChangeListener { _, isChecked -> filterSessionAll.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) { if (isChecked) {
sessionTypeCondition = null sessionTypeCondition = null
@ -183,7 +183,7 @@ class CalendarFragment : SessionObserverFragment(), CoroutineScope, StaticRowRep
} }
} }
// Manage time filter // Manage time queryWith
filterTimeMonth.setOnCheckedChangeListener { _, isChecked -> filterTimeMonth.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) { if (isChecked) {
currentTimeFilter = TimeFilter.MONTH currentTimeFilter = TimeFilter.MONTH

@ -243,7 +243,7 @@ open class FilterDetailsFragment : PokerAnalyticsFragment(), StaticRowRepresent
*/ */
private fun saveData() { private fun saveData() {
//TODO: Save currentFilter details data //TODO: Save currentFilter details data
Timber.d("Save data for filter: ${currentFilter?.id}") Timber.d("Save data for queryWith: ${currentFilter?.id}")
selectedRows.forEach { selectedRows.forEach {
Timber.d("Selected rows: $it") Timber.d("Selected rows: $it")
} }

@ -60,7 +60,7 @@ open class FiltersFragment : PokerAnalyticsFragment(), StaticRowRepresentableDat
Timber.d("onActivityResult: $requestCode") Timber.d("onActivityResult: $requestCode")
if (data != null && data.hasExtra(FilterDetailsActivity.IntentKey.FILTER_ID.keyName)) { if (data != null && data.hasExtra(FilterDetailsActivity.IntentKey.FILTER_ID.keyName)) {
val filterId = data.getStringExtra(FilterDetailsActivity.IntentKey.FILTER_ID.keyName) val filterId = data.getStringExtra(FilterDetailsActivity.IntentKey.FILTER_ID.keyName)
Timber.d("Updated filter: ${filterId}") Timber.d("Updated queryWith: ${filterId}")
} }
*/ */
@ -179,19 +179,19 @@ open class FiltersFragment : PokerAnalyticsFragment(), StaticRowRepresentableDat
} }
/** /**
* Valid the updates of the filter * Valid the updates of the queryWith
*/ */
private fun validUpdates() { private fun validUpdates() {
Timber.d("Valid filter updates") Timber.d("Valid queryWith updates")
val filterId = currentFilter?.id ?: "" val filterId = currentFilter?.id ?: ""
finishActivityWithResult(filterId) finishActivityWithResult(filterId)
} }
/** /**
* Cancel the latest updates of the filter * Cancel the latest updates of the queryWith
*/ */
private fun cancelUpdates() { private fun cancelUpdates() {
Timber.d("Cancel filter updates") Timber.d("Cancel queryWith updates")
val filterId = filterCopy?.id ?: "" val filterId = filterCopy?.id ?: ""
@ -208,7 +208,7 @@ open class FiltersFragment : PokerAnalyticsFragment(), StaticRowRepresentableDat
* Delete data * Delete data
*/ */
private fun deleteFilter() { private fun deleteFilter() {
Timber.d("Delete filter") Timber.d("Delete queryWith")
val realm = getRealm() val realm = getRealm()
realm.beginTransaction() realm.beginTransaction()
currentFilter?.deleteFromRealm() currentFilter?.deleteFromRealm()

@ -4,19 +4,17 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.isVisible
import com.github.mikephil.charting.charts.BarChart import com.github.mikephil.charting.charts.BarChart
import com.github.mikephil.charting.charts.BarLineChartBase import com.github.mikephil.charting.charts.BarLineChartBase
import com.github.mikephil.charting.charts.LineChart import com.github.mikephil.charting.charts.LineChart
import com.github.mikephil.charting.data.Entry import com.github.mikephil.charting.data.*
import com.github.mikephil.charting.data.LineData
import com.github.mikephil.charting.highlight.Highlight import com.github.mikephil.charting.highlight.Highlight
import com.github.mikephil.charting.listener.OnChartValueSelectedListener import com.github.mikephil.charting.listener.OnChartValueSelectedListener
import com.google.android.material.chip.Chip import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipGroup import com.google.android.material.chip.ChipGroup
import kotlinx.android.synthetic.main.fragment_graph.* import io.realm.Realm
import kotlinx.coroutines.CoroutineScope import kotlinx.android.synthetic.main.fragment_evograph.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.*
import net.pokeranalytics.android.R import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.* import net.pokeranalytics.android.calculus.*
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
@ -26,7 +24,8 @@ import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment
import net.pokeranalytics.android.ui.graph.PALineDataSet import net.pokeranalytics.android.ui.graph.PALineDataSet
import net.pokeranalytics.android.ui.graph.setStyle import net.pokeranalytics.android.ui.graph.setStyle
import net.pokeranalytics.android.ui.view.LegendView import net.pokeranalytics.android.ui.view.LegendView
import java.text.DateFormat import timber.log.Timber
import java.util.*
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
class GraphParameters(var stat: Stat, var computableGroup: ComputableGroup, var report: Report) { class GraphParameters(var stat: Stat, var computableGroup: ComputableGroup, var report: Report) {
@ -52,50 +51,39 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener, Co
private var stat: Stat = Stat.NETRESULT private var stat: Stat = Stat.NETRESULT
private var reports: MutableMap<AggregationType, Report> = hashMapOf() private var reports: MutableMap<AggregationType, Report> = hashMapOf()
lateinit private var computableGroup: ComputableGroup
private var computableGroup: ComputableGroup? = null lateinit private var selectedReport: Report
private var selectedReport: Report? = null
private var legendView: LegendView? = null lateinit var legendView: LegendView
private var chartView: BarLineChartBase<*>? = null lateinit var chartView: BarLineChartBase<*>
private var displayAggregationChoices: Boolean = true
private var aggregationTypes: List<AggregationType> = listOf() private var aggregationTypes: List<AggregationType> = listOf()
override val coroutineContext: CoroutineContext override val coroutineContext: CoroutineContext
get() = Dispatchers.Main get() = Dispatchers.Main
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { companion object {
return inflater.inflate(R.layout.fragment_graph, container, false)
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { fun setData(stat: Stat, group: ComputableGroup, report: Report) {
super.onViewCreated(view, savedInstanceState) this.stat = stat
initUI() this.computableGroup = group
selectedReport?.let { this.aggregationTypes = stat.aggregationTypes
loadGraph(it) this.reports[this.aggregationTypes.first()] = report
} this.selectedReport = report
} }
// OnChartValueSelectedListener override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
override fun onNothingSelected() { return inflater.inflate(R.layout.fragment_evograph, container, false)
// nothing to do
} }
override fun onValueSelected(e: Entry?, h: Highlight?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
e?.let { entry -> super.onViewCreated(view, savedInstanceState)
val identifier = entry.data as ObjectIdentifier initUI()
val item = getRealm().where(identifier.clazz).equalTo("id", identifier.id).findAll().firstOrNull()
item?.let {
val formattedDate = DateFormat.getDateInstance(DateFormat.SHORT).format(it.startDate())
val entryValue = it.formattedValue(this.stat, requireContext())
val totalStatValue = this.stat.format(e.y.toDouble(), currency = null, context = requireContext())
this.legendView?.setItemData(this.stat, formattedDate, entryValue, totalStatValue)
}
}
} }
/** /**
@ -109,6 +97,16 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener, Co
this.legendView = LegendView(requireContext()) this.legendView = LegendView(requireContext())
this.legendContainer.addView(this.legendView) this.legendContainer.addView(this.legendView)
this.chartView = when (stat.graphType) {
GraphType.LINE -> LineChart(context)
GraphType.BAR -> BarChart(context)
}
this.chartView.setStyle(false, requireContext())
this.chartContainer.addView(this.chartView)
this.loadGraph(this.aggregationTypes.first(), this.selectedReport)
this.aggregationTypes.forEach { type -> this.aggregationTypes.forEach { type ->
val chip = Chip(requireContext()) val chip = Chip(requireContext())
chip.id = type.ordinal chip.id = type.ordinal
@ -118,7 +116,6 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener, Co
this.chipGroup.addView(chip) this.chipGroup.addView(chip)
} }
this.chipGroup.isVisible = displayAggregationChoices
this.chipGroup.check(this.stat.aggregationTypes.first().ordinal) this.chipGroup.check(this.stat.aggregationTypes.first().ordinal)
this.chipGroup.setOnCheckedChangeListener(object : ChipGroupExtension.SingleSelectionOnCheckedListener() { this.chipGroup.setOnCheckedChangeListener(object : ChipGroupExtension.SingleSelectionOnCheckedListener() {
@ -127,9 +124,9 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener, Co
val aggregationType = aggregationTypes[checkedId] val aggregationType = aggregationTypes[checkedId]
reports[aggregationType]?.let { reports[aggregationType]?.let {
loadGraph(it) loadGraph(aggregationType, it)
} ?: run { } ?: run {
launchStatComputation(aggregationType)
} }
} }
@ -137,8 +134,7 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener, Co
} }
/* private fun launchStatComputation(aggregationType: AggregationType) {
private fun launchStatComputation() {
GlobalScope.launch(coroutineContext) { GlobalScope.launch(coroutineContext) {
@ -149,8 +145,10 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener, Co
val realm = Realm.getDefaultInstance() val realm = Realm.getDefaultInstance()
val aggregationType = stat.aggregationTypes.first() val report = Calculator.computeStatsWithEvolutionByAggregationType(realm, computableGroup, aggregationType)
r = Calculator.computeStatsWithEvolutionByAggregationType(realm, computableGroup, aggregationType) reports[aggregationType] = report
r = report
realm.close() realm.close()
@ -163,61 +161,84 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener, Co
if (!isDetached) { if (!isDetached) {
r?.let { r?.let {
loadGraph(it) loadGraph(aggregationType, it)
} }
} }
} }
} }
*/
/** fun loadGraph(aggregationType: AggregationType, report: Report) {
* Load graph
*/
private fun loadGraph(report: Report) {
report.results.firstOrNull()?.defaultStatEntries(stat)?.let { entries -> val graphEntries = when (aggregationType) {
AggregationType.SESSION, AggregationType.DURATION -> report.results.firstOrNull()?.defaultStatEntries(stat)
AggregationType.MONTH, AggregationType.YEAR -> report.lineEntries(this.stat)
}
this.legendView?.prepareWithStat(this.stat, entries.size) graphEntries?.let { entries ->
val dataSet = PALineDataSet(entries, this.stat.name, requireContext()) this.legendView.prepareWithStat(this.stat, entries.size)
val colors = arrayOf(R.color.green_light).toIntArray()
dataSet.setColors(colors, context)
dataSet.setDrawCircles(false)
dataSet.setDrawValues(false)
val lineData = LineData(listOf(dataSet))
this.chartView = when (stat.graphType) { when (stat.graphType) {
GraphType.LINE -> { GraphType.LINE -> {
val lineChart = LineChart(context) val lineChart: LineChart = this.chartView as LineChart
val dataSet = PALineDataSet(entries, this.stat.name, requireContext())
val colors = arrayOf(R.color.green_light).toIntArray()
dataSet.setColors(colors, context)
dataSet.setDrawCircles(false)
val lineData = LineData(listOf(dataSet))
lineChart.data = lineData lineChart.data = lineData
lineChart
} }
GraphType.BAR -> { GraphType.BAR -> {
val barChart = BarChart(context) val barChart = this.chartView as BarChart
barChart val dataSet = BarDataSet(entries as List<BarEntry>, this.stat.name)
val colors = arrayOf(R.color.green_light).toIntArray()
dataSet.setColors(colors, context)
val barData = BarData(listOf(dataSet))
barChart.data = barData
} }
} }
this.chartContainer.addView(this.chartView) this.chartView.setStyle(false, requireContext())
this.chartView.setOnChartValueSelectedListener(this)
this.chartView?.setStyle(false, requireContext()) this.chartView.highlightValue((entries.size - 1).toFloat(), 0)
this.chartView?.setOnChartValueSelectedListener(this)
this.chartView?.highlightValue((entries.size - 1).toFloat(), 0)
} }
} }
/** // OnChartValueSelectedListener
* Set data
*/ override fun onNothingSelected() {
fun setData(stat: Stat, group: ComputableGroup, report: Report, displayAggregationChoices: Boolean) { // nothing to do
this.stat = stat }
this.computableGroup = group
override fun onValueSelected(e: Entry?, h: Highlight?) {
e?.let { entry ->
val statEntry = when (entry.data) {
is ObjectIdentifier -> {
val identifier = entry.data as ObjectIdentifier
getRealm().where(identifier.clazz).equalTo("id", identifier.id).findAll().firstOrNull()
}
is StatEntry -> entry.data as StatEntry?
else -> null
}
statEntry?.let {
val formattedDate = it.entryTitle
val entryValue = it.formattedValue(this.stat, requireContext())
val totalStatValue = this.stat.format(e.y.toDouble(), currency = null, context = requireContext())
this.legendView.setItemData(this.stat, formattedDate, entryValue, totalStatValue)
}
}
this.aggregationTypes = stat.aggregationTypes
this.reports[this.aggregationTypes.first()] = report
this.selectedReport = report
this.displayAggregationChoices = displayAggregationChoices
} }
} }

@ -223,7 +223,7 @@ class StatsFragment : SessionObserverFragment(), StaticRowRepresentableDataSourc
if (row is StatRow && row.stat.hasEvolutionGraph) { if (row is StatRow && row.stat.hasEvolutionGraph) {
// filter groups // queryWith groups
val groupResults = this.report?.results?.filter { val groupResults = this.report?.results?.filter {
it.group.name == row.groupName it.group.name == row.groupName
} }

@ -9,7 +9,15 @@ class PALineDataSet(yVals: List<Entry>, label: String, context: Context) : LineD
init { init {
this.highLightColor = context.getColor(R.color.chart_highlight_indicator) this.highLightColor = context.getColor(R.color.chart_highlight_indicator)
this.setDrawValues(false)
} }
} }
//class PABarDataSet(yVals: List<BarEntry>, label: String, context: Context) : BarDataSet(yVals, label) {
//
// init {
// this.highLightColor = context.getColor(R.color.chart_highlight_indicator)
// }
//
//}

@ -45,9 +45,9 @@ fun BarLineChartBase<*>.setStyle(small: Boolean, context: Context) {
this.axisRight.isEnabled = false this.axisRight.isEnabled = false
this.legend.isEnabled = false this.legend.isEnabled = false
this.data.isHighlightEnabled = !small
this.description.isEnabled = false this.description.isEnabled = false
this.data?.isHighlightEnabled = !small
// @todo // @todo
// if timeYAxis { // if timeYAxis {

Loading…
Cancel
Save