Work on aggregation type for evo graphs

feature/top10
Laurent 7 years ago
parent 8bbc35ba9a
commit 7447305caf
  1. 70
      app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt
  2. 141
      app/src/main/java/net/pokeranalytics/android/ui/fragment/GraphFragment.kt
  3. 296
      app/src/main/java/net/pokeranalytics/android/ui/fragment/StatsFragment.kt

@ -7,7 +7,6 @@ import net.pokeranalytics.android.model.comparison.combined
import net.pokeranalytics.android.model.filter.QueryCondition import net.pokeranalytics.android.model.filter.QueryCondition
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.Filter
import net.pokeranalytics.android.model.realm.SessionSet import net.pokeranalytics.android.model.realm.SessionSet
import timber.log.Timber import timber.log.Timber
import java.util.* import java.util.*
@ -20,7 +19,11 @@ class Calculator {
/** /**
* The options used for calculations or display * The options used for calculations or display
*/ */
class Options { class Options(
display: Display = Display.TABLE,
evolutionValues: EvolutionValues = EvolutionValues.NONE,
stats: List<Stat> = listOf()
) {
/** /**
* The way the stats are going to be displayed * The way the stats are going to be displayed
@ -42,9 +45,9 @@ class Calculator {
TIMED TIMED
} }
var display: Display = Display.TABLE var display: Display = display
var evolutionValues: EvolutionValues = EvolutionValues.NONE var evolutionValues: EvolutionValues = evolutionValues
var displayedStats: List<Stat> = listOf() var displayedStats: List<Stat> = stats
/** /**
* This function determines whether the standard deviation should be computed * This function determines whether the standard deviation should be computed
@ -64,7 +67,36 @@ class Calculator {
companion object { companion object {
fun computeStatsWithComparators(realm: Realm, comparators: List<Comparator> = listOf(), conditions: List<QueryCondition> = listOf(), options: Options): Report { fun computeStatsWithEvolutionByAggregationType(
realm: Realm,
group: ComputableGroup,
aggregationType: AggregationType
): Report {
val options = Options(evolutionValues = Options.EvolutionValues.STANDARD)
if (aggregationType == AggregationType.DURATION) {
options.evolutionValues = Options.EvolutionValues.TIMED
}
return when (aggregationType) {
AggregationType.SESSION, AggregationType.DURATION -> this.computeGroups(realm, listOf(group), options)
AggregationType.MONTH -> {
val comparators: List<Comparator> = listOf(Comparator.YEAR, Comparator.MONTH_OF_YEAR)
this.computeStatsWithComparators(realm, comparators, group.conditions, options)
}
AggregationType.YEAR -> {
val comparators: List<Comparator> = listOf(Comparator.YEAR)
this.computeStatsWithComparators(realm, comparators, group.conditions, options)
}
}
}
fun computeStatsWithComparators(
realm: Realm,
comparators: List<Comparator> = listOf(),
conditions: List<QueryCondition> = listOf(),
options: Options
): Report {
val computableGroups: MutableList<ComputableGroup> = mutableListOf() val computableGroups: MutableList<ComputableGroup> = mutableListOf()
comparators.combined().forEach { comparatorConditions -> comparators.combined().forEach { comparatorConditions ->
@ -86,18 +118,6 @@ class Calculator {
return this.computeGroups(realm, computableGroups, options) return this.computeGroups(realm, computableGroups, options)
} }
fun computeStatsWithFilters(realm: Realm, filters: List<Filter>, options: Options): Report {
val computableGroups: MutableList<ComputableGroup> = mutableListOf()
filters.forEach { filter ->
val group = ComputableGroup(filter.name, filter.filterConditions.map { it.queryCondition })
computableGroups.add(group)
}
return Calculator.computeGroups(realm, computableGroups, options)
}
/** /**
* Computes all stats for list of Session sessionGroup * Computes all stats for list of Session sessionGroup
*/ */
@ -257,7 +277,12 @@ class Calculator {
Options.EvolutionValues.TIMED -> { Options.EvolutionValues.TIMED -> {
results.addEvolutionValue(tSum, tHourlyDuration, NETRESULT, sessionSet) results.addEvolutionValue(tSum, tHourlyDuration, NETRESULT, sessionSet)
results.addEvolutionValue(tHourlyRate, tHourlyDuration, HOURLY_RATE, sessionSet) results.addEvolutionValue(tHourlyRate, tHourlyDuration, HOURLY_RATE, sessionSet)
results.addEvolutionValue(tIndex.toDouble(), tHourlyDuration, NUMBER_OF_SETS, sessionSet) results.addEvolutionValue(
tIndex.toDouble(),
tHourlyDuration,
NUMBER_OF_SETS,
sessionSet
)
results.addEvolutionValue( results.addEvolutionValue(
sessionSet.netDuration.toDouble(), sessionSet.netDuration.toDouble(),
tHourlyDuration, tHourlyDuration,
@ -273,7 +298,12 @@ 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(gBBSum, gTotalHands)?.let { netBB100 ->
results.addEvolutionValue(netBB100, tHourlyDuration, NET_BB_PER_100_HANDS, sessionSet) results.addEvolutionValue(
netBB100,
tHourlyDuration,
NET_BB_PER_100_HANDS,
sessionSet
)
} }
} }
} }

@ -13,7 +13,9 @@ 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 io.realm.Realm
import kotlinx.android.synthetic.main.fragment_evograph.* import kotlinx.android.synthetic.main.fragment_evograph.*
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
@ -23,25 +25,32 @@ 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 timber.log.Timber
import java.text.DateFormat import java.text.DateFormat
import java.util.*
import kotlin.coroutines.CoroutineContext
class GraphParameters(var stat: Stat, var report: Report) { class GraphParameters(var stat: Stat, var report: Report) {
} }
class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener { class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener, CoroutineScope {
private lateinit var parentActivity: PokerAnalyticsActivity private lateinit var parentActivity: PokerAnalyticsActivity
private var stat: Stat = Stat.NETRESULT private var stat: Stat = Stat.NETRESULT
private var reports: Map<AggregationType, Report> = hashMapOf() private var reports: MutableMap<AggregationType, Report> = hashMapOf()
private var entries: List<Entry> = ArrayList()
private var selectedReport: Report? = null
lateinit var legendView: LegendView lateinit var legendView: LegendView
lateinit var chartView: BarLineChartBase<*> lateinit var chartView: BarLineChartBase<*>
private var aggregationTypes: List<AggregationType> = listOf() private var aggregationTypes: List<AggregationType> = listOf()
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main
companion object { companion object {
} }
@ -50,10 +59,8 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener {
this.stat = stat this.stat = stat
this.aggregationTypes = stat.aggregationTypes this.aggregationTypes = stat.aggregationTypes
// this.report = report this.reports[this.aggregationTypes.first()] = report
report.results.firstOrNull()?.defaultStatEntries(stat)?.let { this.selectedReport = report
this.entries = it
}
} }
@ -76,32 +83,10 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener {
this.legendView = LegendView(requireContext()) this.legendView = LegendView(requireContext())
this.legendContainer.addView(this.legendView) this.legendContainer.addView(this.legendView)
this.legendView.prepareWithStat(this.stat, this.entries.size)
val dataSet = PALineDataSet(this.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))
this.chartView = when (stat.graphType) {
GraphType.LINE -> {
val lineChart = LineChart(context)
lineChart.data = lineData
lineChart
}
GraphType.BAR -> {
val barChart = BarChart(context)
barChart
}
}
this.chartContainer.addView(this.chartView) this.selectedReport?.let {
this.loadGraph(it)
this.chartView.setStyle(false, requireContext()) }
this.chartView.setOnChartValueSelectedListener(this)
this.chartView.highlightValue((this.entries.size - 1).toFloat(), 0)
this.aggregationTypes.forEach { type -> this.aggregationTypes.forEach { type ->
val chip = Chip(requireContext()) val chip = Chip(requireContext())
@ -118,12 +103,85 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener {
override fun onCheckedChanged(group: ChipGroup, checkedId: Int) { override fun onCheckedChanged(group: ChipGroup, checkedId: Int) {
super.onCheckedChanged(group, checkedId) super.onCheckedChanged(group, checkedId)
val aggregationType = aggregationTypes[checkedId] val aggregationType = aggregationTypes[checkedId]
// toast("Show: ${this.aggregationTypes[group.getChildAt(checkedId).id].name}")
reports[aggregationType]?.let {
loadGraph(it)
} ?: run {
}
} }
}) })
} }
private fun launchStatComputation() {
GlobalScope.launch(coroutineContext) {
var r: Report? = null
val test = GlobalScope.async {
val s = Date()
Timber.d(">>> start...")
val realm = Realm.getDefaultInstance()
val aggregationType = stat.aggregationTypes.first()
// r = Calculator.computeStatsWithEvolutionByAggregationType(realm, computableGroup, aggregationType)
realm.close()
val e = Date()
val duration = (e.time - s.time) / 1000.0
Timber.d(">>> ended in $duration seconds")
}
test.await()
if (!isDetached) {
r?.let {
loadGraph(it)
}
}
}
}
fun loadGraph(report: Report) {
report.results.firstOrNull()?.defaultStatEntries(stat)?.let { entries ->
this.legendView.prepareWithStat(this.stat, entries.size)
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))
this.chartView = when (stat.graphType) {
GraphType.LINE -> {
val lineChart = LineChart(context)
lineChart.data = lineData
lineChart
}
GraphType.BAR -> {
val barChart = BarChart(context)
barChart
}
}
this.chartContainer.addView(this.chartView)
this.chartView.setStyle(false, requireContext())
this.chartView.setOnChartValueSelectedListener(this)
this.chartView.highlightValue((entries.size - 1).toFloat(), 0)
}
}
// OnChartValueSelectedListener // OnChartValueSelectedListener
override fun onNothingSelected() { override fun onNothingSelected() {
@ -133,19 +191,18 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener {
override fun onValueSelected(e: Entry?, h: Highlight?) { override fun onValueSelected(e: Entry?, h: Highlight?) {
e?.let { entry -> e?.let { entry ->
h?.let { highlight ->
val identifier = entry.data as ObjectIdentifier val identifier = entry.data as ObjectIdentifier
val item = getRealm().where(identifier.clazz).equalTo("id", identifier.id).findAll().firstOrNull() val item = getRealm().where(identifier.clazz).equalTo("id", identifier.id).findAll().firstOrNull()
item?.let { item?.let {
val formattedDate = DateFormat.getDateInstance(DateFormat.SHORT).format(it.startDate()) val formattedDate = DateFormat.getDateInstance(DateFormat.SHORT).format(it.startDate())
val entryValue = it.formattedValue(this.stat, requireContext()) val entryValue = it.formattedValue(this.stat, requireContext())
val totalStatValue = this.stat.format(e.y.toDouble(), null, requireContext()) val totalStatValue = this.stat.format(e.y.toDouble(), null, requireContext())
this.legendView.setItemData(this.stat, formattedDate, entryValue, totalStatValue) this.legendView.setItemData(this.stat, formattedDate, entryValue, totalStatValue)
}
} }
} }
} }

@ -28,96 +28,96 @@ import kotlin.coroutines.CoroutineContext
class StatsFragment : SessionObserverFragment(), StaticRowRepresentableDataSource, CoroutineScope, class StatsFragment : SessionObserverFragment(), StaticRowRepresentableDataSource, CoroutineScope,
RowRepresentableDelegate { RowRepresentableDelegate {
override val coroutineContext: CoroutineContext override val coroutineContext: CoroutineContext
get() = Dispatchers.Main get() = Dispatchers.Main
private var rowRepresentables: ArrayList<RowRepresentable> = ArrayList() private var rowRepresentables: ArrayList<RowRepresentable> = ArrayList()
private var stringAll = "" private var stringAll = ""
private var stringCashGame = "" private var stringCashGame = ""
private var stringTournament = "" private var stringTournament = ""
private lateinit var statsAdapter: RowRepresentableAdapter private lateinit var statsAdapter: RowRepresentableAdapter
private var report : Report? = null private var report: Report? = null
companion object { companion object {
/** /**
* Create new instance * Create new instance
*/ */
fun newInstance(): StatsFragment { fun newInstance(): StatsFragment {
val fragment = StatsFragment() val fragment = StatsFragment()
val bundle = Bundle() val bundle = Bundle()
fragment.arguments = bundle fragment.arguments = bundle
return fragment return fragment
} }
} }
// Life Cycle // Life Cycle
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_stats, container, false) return inflater.inflate(R.layout.fragment_stats, container, false)
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
initData() initData()
initUI() initUI()
launchStatComputation() launchStatComputation()
} }
// Row Representable DS // Row Representable DS
override fun adapterRows(): List<RowRepresentable>? { override fun adapterRows(): List<RowRepresentable>? {
return this.rowRepresentables return this.rowRepresentables
} }
override fun contentDescriptorForRow(row: RowRepresentable): DisplayDescriptor? { override fun contentDescriptorForRow(row: RowRepresentable): DisplayDescriptor? {
val dc = DisplayDescriptor() val dc = DisplayDescriptor()
dc.textFormat = TextFormat(NULL_TEXT) dc.textFormat = TextFormat(NULL_TEXT)
if (row is StatRepresentable) { if (row is StatRepresentable) {
context?.let { context -> context?.let { context ->
row.computedStat?.let { row.computedStat?.let {
dc.textFormat = it.format(context) dc.textFormat = it.format(context)
} }
} }
} }
return dc return dc
} }
override fun statFormatForRow(row: RowRepresentable): TextFormat { override fun statFormatForRow(row: RowRepresentable): TextFormat {
if (row is StatRepresentable) { if (row is StatRepresentable) {
context?.let { context -> context?.let { context ->
row.computedStat?.let { return it.format(context) } row.computedStat?.let { return it.format(context) }
} }
} }
return TextFormat(NULL_TEXT) return TextFormat(NULL_TEXT)
} }
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
statsAdapter.notifyDataSetChanged() statsAdapter.notifyDataSetChanged()
} }
// Override // Override
override fun sessionsChanged() { override fun sessionsChanged() {
this.launchStatComputation() this.launchStatComputation()
this.statsAdapter.notifyDataSetChanged() this.statsAdapter.notifyDataSetChanged()
} }
// Business // Business
/** /**
* Init data * Init data
*/ */
private fun initData() { private fun initData() {
this.stringAll = getString(R.string.all) this.stringAll = getString(R.string.all)
this.stringCashGame = getString(R.string.cash_game) this.stringCashGame = getString(R.string.cash_game)
this.stringTournament = getString(R.string.tournament) this.stringTournament = getString(R.string.tournament)
this.statsAdapter = RowRepresentableAdapter(this, this) this.statsAdapter = RowRepresentableAdapter(this, this)
} }
/** /**
* Init UI * Init UI
@ -133,67 +133,89 @@ class StatsFragment : SessionObserverFragment(), StaticRowRepresentableDataSourc
} }
} }
private fun launchStatComputation() { private fun launchStatComputation() {
GlobalScope.launch(coroutineContext) { GlobalScope.launch(coroutineContext) {
var r = Report()
val test = GlobalScope.async {
val s = Date()
Timber.d(">>> start...")
val realm = Realm.getDefaultInstance()
r = createSessionGroupsAndStartCompute(realm)
report = r
realm.close()
val e = Date()
val duration = (e.time - s.time) / 1000.0
Timber.d(">>> ended in $duration seconds")
} var r = Report()
test.await() val test = GlobalScope.async {
val s = Date()
Timber.d(">>> start...")
if (!isDetached) { val realm = Realm.getDefaultInstance()
showResults(r) r = createSessionGroupsAndStartCompute(realm)
} report = r
} realm.close()
} val e = Date()
val duration = (e.time - s.time) / 1000.0
Timber.d(">>> ended in $duration seconds")
private fun createSessionGroupsAndStartCompute(realm: Realm) : Report { }
test.await()
val allStats: List<Stat> = listOf(Stat.NETRESULT, Stat.HOURLY_RATE, Stat.AVERAGE, Stat.NUMBER_OF_SETS, Stat.AVERAGE_DURATION, Stat.DURATION) if (!isDetached) {
val allSessionGroup = ComputableGroup(stringAll, listOf(), allStats) showResults(r)
val cgStats: List<Stat> = listOf(Stat.NETRESULT, Stat.HOURLY_RATE, Stat.NET_BB_PER_100_HANDS, Stat.HOURLY_RATE_BB, Stat.AVERAGE, Stat.STANDARD_DEVIATION_HOURLY, Stat.WIN_RATIO, Stat.NUMBER_OF_GAMES, Stat.AVERAGE_BUYIN) }
val cgSessionGroup = ComputableGroup(stringCashGame, listOf(QueryCondition.CASH), cgStats) }
val tStats: List<Stat> = listOf(Stat.NETRESULT, Stat.HOURLY_RATE, Stat.ROI, Stat.WIN_RATIO, Stat.NUMBER_OF_GAMES, Stat.AVERAGE_BUYIN)
val tSessionGroup = ComputableGroup(stringTournament, listOf(QueryCondition.TOURNAMENT), tStats)
Timber.d(">>>>> Start computations...") }
return Calculator.computeGroups(realm, listOf(allSessionGroup, cgSessionGroup, tSessionGroup), Calculator.Options()) private fun createSessionGroupsAndStartCompute(realm: Realm): Report {
val allStats: List<Stat> = listOf(
Stat.NETRESULT,
Stat.HOURLY_RATE,
Stat.AVERAGE,
Stat.NUMBER_OF_SETS,
Stat.AVERAGE_DURATION,
Stat.DURATION
)
val allSessionGroup = ComputableGroup(stringAll, listOf(), allStats)
val cgStats: List<Stat> = listOf(
Stat.NETRESULT,
Stat.HOURLY_RATE,
Stat.NET_BB_PER_100_HANDS,
Stat.HOURLY_RATE_BB,
Stat.AVERAGE,
Stat.STANDARD_DEVIATION_HOURLY,
Stat.WIN_RATIO,
Stat.NUMBER_OF_GAMES,
Stat.AVERAGE_BUYIN
)
val cgSessionGroup = ComputableGroup(stringCashGame, listOf(QueryCondition.CASH), cgStats)
val tStats: List<Stat> =
listOf(Stat.NETRESULT, Stat.HOURLY_RATE, Stat.ROI, Stat.WIN_RATIO, Stat.NUMBER_OF_GAMES, Stat.AVERAGE_BUYIN)
val tSessionGroup = ComputableGroup(stringTournament, listOf(QueryCondition.TOURNAMENT), tStats)
Timber.d(">>>>> Start computations...")
return Calculator.computeGroups(
realm,
listOf(allSessionGroup, cgSessionGroup, tSessionGroup),
Calculator.Options()
)
} }
private fun showResults(report: Report) { private fun showResults(report: Report) {
this.rowRepresentables = this.convertReportIntoRepresentables(report) this.rowRepresentables = this.convertReportIntoRepresentables(report)
statsAdapter.notifyDataSetChanged() statsAdapter.notifyDataSetChanged()
} }
private fun convertReportIntoRepresentables(report: Report) : ArrayList<RowRepresentable> { private fun convertReportIntoRepresentables(report: Report): ArrayList<RowRepresentable> {
val rows: ArrayList<RowRepresentable> = ArrayList() val rows: ArrayList<RowRepresentable> = ArrayList()
report.results.forEach { result -> report.results.forEach { result ->
rows.add(CustomizableRowRepresentable(title = result.group.name)) rows.add(CustomizableRowRepresentable(title = result.group.name))
result.group.stats?.forEach { stat -> result.group.stats?.forEach { stat ->
rows.add(StatRepresentable(stat, result.computedStat(stat), result.group.name)) rows.add(StatRepresentable(stat, result.computedStat(stat), result.group.name))
} }
} }
return rows return rows
} }
// RowRepresentableDelegate // RowRepresentableDelegate
@ -223,10 +245,14 @@ class StatsFragment : SessionObserverFragment(), StaticRowRepresentableDataSourc
Timber.d(">>> start...") Timber.d(">>> start...")
val realm = Realm.getDefaultInstance() val realm = Realm.getDefaultInstance()
val options = Calculator.Options() // val options = Calculator.Options()
options.evolutionValues = Calculator.Options.EvolutionValues.STANDARD // options.evolutionValues = Calculator.Options.EvolutionValues.STANDARD
options.displayedStats = listOf(stat) // options.displayedStats = listOf(stat)
report = Calculator.computeGroups(realm, listOf(computableGroup), options)
val aggregationType = stat.aggregationTypes.first()
report = Calculator.computeStatsWithEvolutionByAggregationType(realm, computableGroup, aggregationType)
// report = Calculator.computeGroups(realm, listOf(computableGroup), options)
realm.close() realm.close()
val e = Date() val e = Date()

Loading…
Cancel
Save