diff --git a/app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt b/app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt index f7f42459..ffd17417 100644 --- a/app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt +++ b/app/src/main/java/net/pokeranalytics/android/calculus/Calculator.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.name import net.pokeranalytics.android.model.realm.ComputableResult -import net.pokeranalytics.android.model.realm.Filter import net.pokeranalytics.android.model.realm.SessionSet import timber.log.Timber import java.util.* @@ -20,7 +19,11 @@ class Calculator { /** * The options used for calculations or display */ - class Options { + class Options( + display: Display = Display.TABLE, + evolutionValues: EvolutionValues = EvolutionValues.NONE, + stats: List = listOf() + ) { /** * The way the stats are going to be displayed @@ -42,9 +45,9 @@ class Calculator { TIMED } - var display: Display = Display.TABLE - var evolutionValues: EvolutionValues = EvolutionValues.NONE - var displayedStats: List = listOf() + var display: Display = display + var evolutionValues: EvolutionValues = evolutionValues + var displayedStats: List = stats /** * This function determines whether the standard deviation should be computed @@ -64,7 +67,36 @@ class Calculator { companion object { - fun computeStatsWithComparators(realm: Realm, comparators: List = listOf(), conditions: List = 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 = listOf(Comparator.YEAR, Comparator.MONTH_OF_YEAR) + this.computeStatsWithComparators(realm, comparators, group.conditions, options) + } + AggregationType.YEAR -> { + val comparators: List = listOf(Comparator.YEAR) + this.computeStatsWithComparators(realm, comparators, group.conditions, options) + } + } + } + + fun computeStatsWithComparators( + realm: Realm, + comparators: List = listOf(), + conditions: List = listOf(), + options: Options + ): Report { val computableGroups: MutableList = mutableListOf() comparators.combined().forEach { comparatorConditions -> @@ -86,18 +118,6 @@ class Calculator { return this.computeGroups(realm, computableGroups, options) } - fun computeStatsWithFilters(realm: Realm, filters: List, options: Options): Report { - - val computableGroups: MutableList = 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 */ @@ -257,7 +277,12 @@ class Calculator { Options.EvolutionValues.TIMED -> { results.addEvolutionValue(tSum, tHourlyDuration, NETRESULT, 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( sessionSet.netDuration.toDouble(), tHourlyDuration, @@ -273,7 +298,12 @@ class Calculator { results.addEvolutionValue(tHourlyRateBB, tHourlyDuration, HOURLY_RATE_BB, sessionSet) 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 + ) } } } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/GraphFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/GraphFragment.kt index 6479fe10..9f3d5794 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/GraphFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/GraphFragment.kt @@ -13,7 +13,9 @@ import com.github.mikephil.charting.highlight.Highlight import com.github.mikephil.charting.listener.OnChartValueSelectedListener import com.google.android.material.chip.Chip import com.google.android.material.chip.ChipGroup +import io.realm.Realm import kotlinx.android.synthetic.main.fragment_evograph.* +import kotlinx.coroutines.* import net.pokeranalytics.android.R import net.pokeranalytics.android.calculus.* 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.setStyle import net.pokeranalytics.android.ui.view.LegendView +import timber.log.Timber import java.text.DateFormat +import java.util.* +import kotlin.coroutines.CoroutineContext class GraphParameters(var stat: Stat, var report: Report) { } -class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener { +class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener, CoroutineScope { private lateinit var parentActivity: PokerAnalyticsActivity private var stat: Stat = Stat.NETRESULT - private var reports: Map = hashMapOf() - private var entries: List = ArrayList() + private var reports: MutableMap = hashMapOf() + + private var selectedReport: Report? = null lateinit var legendView: LegendView lateinit var chartView: BarLineChartBase<*> private var aggregationTypes: List = listOf() + override val coroutineContext: CoroutineContext + get() = Dispatchers.Main + companion object { } @@ -50,10 +59,8 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener { this.stat = stat this.aggregationTypes = stat.aggregationTypes -// this.report = report - report.results.firstOrNull()?.defaultStatEntries(stat)?.let { - this.entries = it - } + this.reports[this.aggregationTypes.first()] = report + this.selectedReport = report } @@ -76,32 +83,10 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener { this.legendView = LegendView(requireContext()) 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.chartView.setStyle(false, requireContext()) - this.chartView.setOnChartValueSelectedListener(this) - - this.chartView.highlightValue((this.entries.size - 1).toFloat(), 0) + this.selectedReport?.let { + this.loadGraph(it) + } this.aggregationTypes.forEach { type -> val chip = Chip(requireContext()) @@ -118,12 +103,85 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener { override fun onCheckedChanged(group: ChipGroup, checkedId: Int) { super.onCheckedChanged(group, 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 override fun onNothingSelected() { @@ -133,19 +191,18 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener { override fun onValueSelected(e: Entry?, h: Highlight?) { e?.let { entry -> - h?.let { highlight -> - val identifier = entry.data as ObjectIdentifier - val item = getRealm().where(identifier.clazz).equalTo("id", identifier.id).findAll().firstOrNull() - item?.let { + val identifier = entry.data as ObjectIdentifier + 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(), null, requireContext()) + val formattedDate = DateFormat.getDateInstance(DateFormat.SHORT).format(it.startDate()) + val entryValue = it.formattedValue(this.stat, 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) } + } } diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/StatsFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/StatsFragment.kt index 4c0c34e2..84f3cd80 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/StatsFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/StatsFragment.kt @@ -28,96 +28,96 @@ import kotlin.coroutines.CoroutineContext class StatsFragment : SessionObserverFragment(), StaticRowRepresentableDataSource, CoroutineScope, RowRepresentableDelegate { - override val coroutineContext: CoroutineContext - get() = Dispatchers.Main + override val coroutineContext: CoroutineContext + get() = Dispatchers.Main - private var rowRepresentables: ArrayList = ArrayList() + private var rowRepresentables: ArrayList = ArrayList() private var stringAll = "" private var stringCashGame = "" private var stringTournament = "" - private lateinit var statsAdapter: RowRepresentableAdapter - private var report : Report? = null - - companion object { - - /** - * Create new instance - */ - fun newInstance(): StatsFragment { - val fragment = StatsFragment() - val bundle = Bundle() - fragment.arguments = bundle - return fragment - } - } - - // Life Cycle - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_stats, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - initData() - initUI() - launchStatComputation() - } - - // Row Representable DS - - override fun adapterRows(): List? { - return this.rowRepresentables - } - - override fun contentDescriptorForRow(row: RowRepresentable): DisplayDescriptor? { - val dc = DisplayDescriptor() - dc.textFormat = TextFormat(NULL_TEXT) - if (row is StatRepresentable) { - context?.let { context -> - row.computedStat?.let { - dc.textFormat = it.format(context) - } - } - } - return dc - } - - override fun statFormatForRow(row: RowRepresentable): TextFormat { - if (row is StatRepresentable) { - context?.let { context -> - row.computedStat?.let { return it.format(context) } - } - } - return TextFormat(NULL_TEXT) - } - - override fun onResume() { - super.onResume() - statsAdapter.notifyDataSetChanged() - } - - // Override - - override fun sessionsChanged() { - this.launchStatComputation() - this.statsAdapter.notifyDataSetChanged() - } - - // Business - - /** - * Init data - */ - private fun initData() { - - this.stringAll = getString(R.string.all) - this.stringCashGame = getString(R.string.cash_game) - this.stringTournament = getString(R.string.tournament) - - this.statsAdapter = RowRepresentableAdapter(this, this) - } + private lateinit var statsAdapter: RowRepresentableAdapter + private var report: Report? = null + + companion object { + + /** + * Create new instance + */ + fun newInstance(): StatsFragment { + val fragment = StatsFragment() + val bundle = Bundle() + fragment.arguments = bundle + return fragment + } + } + + // Life Cycle + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.fragment_stats, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + initData() + initUI() + launchStatComputation() + } + + // Row Representable DS + + override fun adapterRows(): List? { + return this.rowRepresentables + } + + override fun contentDescriptorForRow(row: RowRepresentable): DisplayDescriptor? { + val dc = DisplayDescriptor() + dc.textFormat = TextFormat(NULL_TEXT) + if (row is StatRepresentable) { + context?.let { context -> + row.computedStat?.let { + dc.textFormat = it.format(context) + } + } + } + return dc + } + + override fun statFormatForRow(row: RowRepresentable): TextFormat { + if (row is StatRepresentable) { + context?.let { context -> + row.computedStat?.let { return it.format(context) } + } + } + return TextFormat(NULL_TEXT) + } + + override fun onResume() { + super.onResume() + statsAdapter.notifyDataSetChanged() + } + + // Override + + override fun sessionsChanged() { + this.launchStatComputation() + this.statsAdapter.notifyDataSetChanged() + } + + // Business + + /** + * Init data + */ + private fun initData() { + + this.stringAll = getString(R.string.all) + this.stringCashGame = getString(R.string.cash_game) + this.stringTournament = getString(R.string.tournament) + + this.statsAdapter = RowRepresentableAdapter(this, this) + } /** * Init UI @@ -133,67 +133,89 @@ class StatsFragment : SessionObserverFragment(), StaticRowRepresentableDataSourc } } - private fun launchStatComputation() { + private fun launchStatComputation() { - 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") + GlobalScope.launch(coroutineContext) { - } - test.await() + var r = Report() + val test = GlobalScope.async { + val s = Date() + Timber.d(">>> start...") - if (!isDetached) { - showResults(r) - } - } + 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") - private fun createSessionGroupsAndStartCompute(realm: Realm) : Report { + } + test.await() - val allStats: List = 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 = 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 = 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) + if (!isDetached) { + showResults(r) + } + } - Timber.d(">>>>> Start computations...") + } - return Calculator.computeGroups(realm, listOf(allSessionGroup, cgSessionGroup, tSessionGroup), Calculator.Options()) + private fun createSessionGroupsAndStartCompute(realm: Realm): Report { + + val allStats: List = 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 = 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 = + 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) { - this.rowRepresentables = this.convertReportIntoRepresentables(report) + private fun showResults(report: Report) { + this.rowRepresentables = this.convertReportIntoRepresentables(report) statsAdapter.notifyDataSetChanged() - } + } - private fun convertReportIntoRepresentables(report: Report) : ArrayList { + private fun convertReportIntoRepresentables(report: Report): ArrayList { - val rows: ArrayList = ArrayList() + val rows: ArrayList = ArrayList() - report.results.forEach { result -> - rows.add(CustomizableRowRepresentable(title = result.group.name)) - result.group.stats?.forEach { stat -> - rows.add(StatRepresentable(stat, result.computedStat(stat), result.group.name)) - } - } + report.results.forEach { result -> + rows.add(CustomizableRowRepresentable(title = result.group.name)) + result.group.stats?.forEach { stat -> + rows.add(StatRepresentable(stat, result.computedStat(stat), result.group.name)) + } + } - return rows - } + return rows + } // RowRepresentableDelegate @@ -223,10 +245,14 @@ class StatsFragment : SessionObserverFragment(), StaticRowRepresentableDataSourc Timber.d(">>> start...") val realm = Realm.getDefaultInstance() - val options = Calculator.Options() - options.evolutionValues = Calculator.Options.EvolutionValues.STANDARD - options.displayedStats = listOf(stat) - report = Calculator.computeGroups(realm, listOf(computableGroup), options) +// val options = Calculator.Options() +// options.evolutionValues = Calculator.Options.EvolutionValues.STANDARD +// options.displayedStats = listOf(stat) + + val aggregationType = stat.aggregationTypes.first() + report = Calculator.computeStatsWithEvolutionByAggregationType(realm, computableGroup, aggregationType) + +// report = Calculator.computeGroups(realm, listOf(computableGroup), options) realm.close() val e = Date()