diff --git a/app/src/main/java/net/pokeranalytics/android/calcul/ComputedResultsExtensions.kt b/app/src/main/java/net/pokeranalytics/android/calcul/ComputedResultsExtensions.kt new file mode 100644 index 00000000..b232713c --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/calcul/ComputedResultsExtensions.kt @@ -0,0 +1,84 @@ +package net.pokeranalytics.android.calcul + +import android.content.Context +import com.github.mikephil.charting.data.* +import net.pokeranalytics.android.R +import net.pokeranalytics.android.calculus.ComputedResults +import net.pokeranalytics.android.calculus.Point +import net.pokeranalytics.android.calculus.Stat +import net.pokeranalytics.android.ui.graph.DataSetFactory +import kotlin.math.abs + + +// MPAndroidChart + +fun ComputedResults.defaultStatEntries(stat: Stat, context: Context): DataSet { + return when (stat) { + Stat.NUMBER_OF_SETS, Stat.NUMBER_OF_GAMES, Stat.HOURLY_DURATION -> this.barEntries(stat, context = context) + Stat.STANDARD_DEVIATION -> this.distributionEntries(stat, context) + else -> this.singleLineEntries(stat, context) + } +} + +fun ComputedResults.singleLineEntries(stat: Stat, context: Context): LineDataSet { + val entries = mutableListOf() + this.evolutionValues[stat]?.let { points -> + points.forEachIndexed { index, p -> + entries.add(Entry(index.toFloat(), p.y.toFloat(), p.data)) + } + } + return DataSetFactory.lineDataSetInstance(entries, this.group.query.getName(context), context) +} + +fun ComputedResults.durationEntries(stat: Stat, context: Context): LineDataSet { + val entries = mutableListOf() + this.evolutionValues[stat]?.let { points -> + points.forEach { p -> + entries.add(Entry(p.x.toFloat(), p.y.toFloat(), p.data)) + } + } + return DataSetFactory.lineDataSetInstance(entries, stat.name, context) +} + +private fun ComputedResults.barEntries(stat: Stat, context: Context): BarDataSet { + + val entries = mutableListOf() + this.evolutionValues[stat]?.let { points -> + points.forEach { p -> + entries.add(BarEntry(p.x.toFloat(), p.y.toFloat(), p.data)) + } + } + return DataSetFactory.barDataSetInstance(entries, stat.name, context) +} + +private fun ComputedResults.distributionEntries(stat: Stat, context: Context): BarDataSet { + + val colors = mutableListOf() + val entries = mutableListOf() + this.evolutionValues[stat]?.let { points -> + + val negative = mutableListOf() + val positive = mutableListOf() + + points.sortByDescending { it.y } + points.forEach { + if (it.y < 0) { + negative.add(it) + } else { + positive.add(it) + } + } + + negative.forEachIndexed { index, p -> + entries.add(BarEntry(index.toFloat(), abs(p.y.toFloat()), p.data)) + colors.add(context.getColor(R.color.red)) + } + positive.forEachIndexed { index, p -> + val x = negative.size + index.toFloat() + entries.add(BarEntry(x, p.y.toFloat(), p.data)) + colors.add(context.getColor(R.color.green)) + } + + } + return DataSetFactory.barDataSetInstance(entries, stat.name, context, colors) +} diff --git a/app/src/main/java/net/pokeranalytics/android/calcul/ReportExtensions.kt b/app/src/main/java/net/pokeranalytics/android/calcul/ReportExtensions.kt new file mode 100644 index 00000000..ab4d639b --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/calcul/ReportExtensions.kt @@ -0,0 +1,64 @@ +package net.pokeranalytics.android.calcul + +import android.content.Context +import com.github.mikephil.charting.data.BarDataSet +import com.github.mikephil.charting.data.BarEntry +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.data.LineDataSet +import net.pokeranalytics.android.calculus.Report +import net.pokeranalytics.android.calculus.Stat +import net.pokeranalytics.android.ui.graph.DataSetFactory +import net.pokeranalytics.android.util.ColorUtils + + +/** + * Returns the list of entries corresponding to the provided [stat] + * One value will be returned by result + */ +fun Report.lineEntries(stat: Stat? = null, context: Context): LineDataSet { + val entries = mutableListOf() + val statToUse = stat ?: this.options.stats.firstOrNull() + val statName = statToUse?.name ?: "" + + statToUse?.let { + this.results.forEachIndexed { index, results -> + results.computedStat(it)?.progressValue?.let { progressValue -> + entries.add(Entry(index.toFloat(), progressValue.toFloat(), results)) + } + } + } + + return DataSetFactory.lineDataSetInstance(entries, statName, context) +} + +fun Report.barEntries(stat: Stat? = null, context: Context): BarDataSet { + val entries = mutableListOf() + val statToUse = stat ?: options.stats.firstOrNull() + + statToUse?.let { + this.results.forEachIndexed { index, results -> + val cs = results.computedStat(it) + cs?.let { computedStat -> + val barEntry = BarEntry(index.toFloat(), computedStat.value.toFloat(), results) + entries.add(barEntry) + } + } + } + + val label = statToUse?.name ?: "" + return DataSetFactory.barDataSetInstance(entries, label, context) +} + +fun Report.multiLineEntries(context: Context): List { + val dataSets = mutableListOf() + + options.stats.forEach { stat -> + this.results.forEachIndexed { index, result -> + val ds = result.singleLineEntries(stat, context) + ds.color = ColorUtils.almostRandomColor(index, context) + dataSets.add(ds) + } + } + + return dataSets +} diff --git a/app/src/main/java/net/pokeranalytics/android/calculus/AggregationType.kt b/app/src/main/java/net/pokeranalytics/android/calculus/AggregationType.kt index d29e7049..b1318acb 100644 --- a/app/src/main/java/net/pokeranalytics/android/calculus/AggregationType.kt +++ b/app/src/main/java/net/pokeranalytics/android/calculus/AggregationType.kt @@ -2,7 +2,12 @@ package net.pokeranalytics.android.calculus import net.pokeranalytics.android.R import net.pokeranalytics.android.model.Criteria -import net.pokeranalytics.android.ui.graph.AxisFormatting + +enum class AxisFormatting { + DEFAULT, + X_DURATION, + Y_DURATION, +} enum class AggregationType { SESSION, diff --git a/app/src/main/java/net/pokeranalytics/android/calculus/ComputableGroup.kt b/app/src/main/java/net/pokeranalytics/android/calculus/ComputableGroup.kt new file mode 100644 index 00000000..eae144f3 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/calculus/ComputableGroup.kt @@ -0,0 +1,90 @@ +package net.pokeranalytics.android.calculus + +import io.realm.Realm +import io.realm.RealmResults +import net.pokeranalytics.android.model.filter.Query +import net.pokeranalytics.android.model.filter.QueryCondition +import net.pokeranalytics.android.model.realm.ComputableResult +import net.pokeranalytics.android.model.realm.Filter +import net.pokeranalytics.android.model.realm.SessionSet + + +/** + * A sessionGroup of computable items identified by a name + */ +class ComputableGroup(var query: Query, var stats: List? = null) { + + /** + * A subgroup used to compute stat variation + */ + var comparedGroup: ComputableGroup? = null + + /** + * The computed statIds of the comparable sessionGroup + */ + var comparedComputedResults: ComputedResults? = null + + /** + * A list of _conditions to get + */ + val conditions: List + get() { + return this.query.conditions + } + + /** + * The list of endedSessions to compute + */ + private var _computables: RealmResults? = null + + /** + * Retrieves the computables on the relative [realm] filtered with the provided [conditions] + */ + fun computables(realm: Realm, sorted: Boolean = false): RealmResults { + + // if computables exists and is valid (previous realm not closed) + this._computables?.let { + if (it.isValid) { + return it + } + } + + val sortedField = if (sorted) "session.startDate" else null + val computables = Filter.queryOn(realm, this.query, sortedField) + this._computables = computables + return computables + } + + /** + * The list of sets to compute + */ + private var _sessionSets: RealmResults? = null + + /** + * Retrieves the session sets on the relative [realm] filtered with the provided [conditions] + */ + fun sessionSets(realm: Realm, sorted: Boolean = false): RealmResults { + // if computables exists and is valid (previous realm not closed) + this._sessionSets?.let { + if (it.isValid) { + return it + } + } + + val sortedField = if (sorted) SessionSet.Field.START_DATE.identifier else null + val sets = Filter.queryOn(realm, this.query, sortedField) + this._sessionSets = sets + return sets + } + + fun cleanup() { + this._computables = null + this._sessionSets = null + } + + val isEmpty: Boolean + get() { + return this._computables?.isEmpty() ?: true + } + +} diff --git a/app/src/main/java/net/pokeranalytics/android/calculus/Report.kt b/app/src/main/java/net/pokeranalytics/android/calculus/Report.kt index 3d7c06cd..a69a5208 100644 --- a/app/src/main/java/net/pokeranalytics/android/calculus/Report.kt +++ b/app/src/main/java/net/pokeranalytics/android/calculus/Report.kt @@ -1,25 +1,14 @@ package net.pokeranalytics.android.calculus import android.content.Context -import com.github.mikephil.charting.data.* -import io.realm.Realm -import io.realm.RealmResults -import net.pokeranalytics.android.R +import com.github.mikephil.charting.data.Entry import net.pokeranalytics.android.exceptions.PAIllegalStateException -import net.pokeranalytics.android.model.filter.Query -import net.pokeranalytics.android.model.filter.QueryCondition import net.pokeranalytics.android.model.interfaces.GraphIdentifiableEntry -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.ui.fragment.GraphFragment -import net.pokeranalytics.android.ui.graph.DataSetFactory import net.pokeranalytics.android.ui.graph.GraphUnderlyingEntry import net.pokeranalytics.android.ui.view.DefaultLegendValues import net.pokeranalytics.android.ui.view.LegendContent -import net.pokeranalytics.android.util.ColorUtils import net.pokeranalytics.android.util.TextFormat -import kotlin.math.abs /** * The class returned after performing calculation in the Calculator object @@ -43,138 +32,11 @@ class Report(var options: Calculator.Options) { this._results.add(result) } - /** - * Returns the list of entries corresponding to the provided [stat] - * One value will be returned by result - */ - fun lineEntries(stat: Stat? = null, context: Context): LineDataSet { - val entries = mutableListOf() - val statToUse = stat ?: options.stats.firstOrNull() - val statName = statToUse?.name ?: "" - - statToUse?.let { - this._results.forEachIndexed { index, results -> - results.computedStat(it)?.progressValue?.let { progressValue -> - entries.add(Entry(index.toFloat(), progressValue.toFloat(), results)) - } - } - } - - return DataSetFactory.lineDataSetInstance(entries, statName, context) - } - - fun barEntries(stat: Stat? = null, context: Context): BarDataSet { - val entries = mutableListOf() - val statToUse = stat ?: options.stats.firstOrNull() - - statToUse?.let { - this._results.forEachIndexed { index, results -> - val cs = results.computedStat(it) - cs?.let { computedStat -> - val barEntry = BarEntry(index.toFloat(), computedStat.value.toFloat(), results) - entries.add(barEntry) - } - } - } - - val label = statToUse?.name ?: "" - return DataSetFactory.barDataSetInstance(entries, label, context) - } - - fun multiLineEntries(context: Context): List { - val dataSets = mutableListOf() - - options.stats.forEach { stat -> - this._results.forEachIndexed { index, result -> - val ds = result.singleLineEntries(stat, context) - ds.color = ColorUtils.almostRandomColor(index, context) - dataSets.add(ds) - } - } - - return dataSets - } - } -/** - * A sessionGroup of computable items identified by a name - */ -class ComputableGroup(var query: Query, var stats: List? = null) { - - /** - * A subgroup used to compute stat variation - */ - var comparedGroup: ComputableGroup? = null - - /** - * The computed statIds of the comparable sessionGroup - */ - var comparedComputedResults: ComputedResults? = null - - /** - * A list of _conditions to get - */ - val conditions: List - get() { - return this.query.conditions - } - - /** - * The list of endedSessions to compute - */ - private var _computables: RealmResults? = null - - /** - * Retrieves the computables on the relative [realm] filtered with the provided [conditions] - */ - fun computables(realm: Realm, sorted: Boolean = false): RealmResults { - - // if computables exists and is valid (previous realm not closed) - this._computables?.let { - if (it.isValid) { - return it - } - } - - val sortedField = if (sorted) "session.startDate" else null - val computables = Filter.queryOn(realm, this.query, sortedField) - this._computables = computables - return computables - } - - /** - * The list of sets to compute - */ - private var _sessionSets: RealmResults? = null - - /** - * Retrieves the session sets on the relative [realm] filtered with the provided [conditions] - */ - fun sessionSets(realm: Realm, sorted: Boolean = false): RealmResults { - // if computables exists and is valid (previous realm not closed) - this._sessionSets?.let { - if (it.isValid) { - return it - } - } - - val sortedField = if (sorted) SessionSet.Field.START_DATE.identifier else null - val sets = Filter.queryOn(realm, this.query, sortedField) - this._sessionSets = sets - return sets - } - - fun cleanup() { - this._computables = null - this._sessionSets = null - } - - val isEmpty: Boolean - get() { - return this._computables?.isEmpty() ?: true - } +class Point(val x: Double, val y: Double, val data: Any) { + constructor(y: Double, data: Any) : this(0.0, y, data) } class ComputedResults(group: ComputableGroup, @@ -196,6 +58,10 @@ class ComputedResults(group: ComputableGroup, private val allStats: Collection get() { return this._computedStats.values } + val evolutionValues: Map> + get() { + return this._evolutionValues + } /** * Adds a value to the evolution values @@ -355,79 +221,6 @@ class ComputedResults(group: ComputableGroup, this.consolidateProgressStats() } - // MPAndroidChart - - fun defaultStatEntries(stat: Stat, context: Context): DataSet { - return when (stat) { - Stat.NUMBER_OF_SETS, Stat.NUMBER_OF_GAMES, Stat.HOURLY_DURATION -> this.barEntries(stat, context = context) - Stat.STANDARD_DEVIATION -> this.distributionEntries(stat, context) - else -> this.singleLineEntries(stat, context) - } - } - - fun singleLineEntries(stat: Stat, context: Context): LineDataSet { - val entries = mutableListOf() - this._evolutionValues[stat]?.let { points -> - points.forEachIndexed { index, p -> - entries.add(Entry(index.toFloat(), p.y.toFloat(), p.data)) - } - } - return DataSetFactory.lineDataSetInstance(entries, this.group.query.getName(context), context) - } - - fun durationEntries(stat: Stat, context: Context): LineDataSet { - val entries = mutableListOf() - this._evolutionValues[stat]?.let { points -> - points.forEach { p -> - entries.add(Entry(p.x.toFloat(), p.y.toFloat(), p.data)) - } - } - return DataSetFactory.lineDataSetInstance(entries, stat.name, context) - } - - private fun barEntries(stat: Stat, context: Context): BarDataSet { - - val entries = mutableListOf() - this._evolutionValues[stat]?.let { points -> - points.forEach { p -> - entries.add(BarEntry(p.x.toFloat(), p.y.toFloat(), p.data)) - } - } - return DataSetFactory.barDataSetInstance(entries, stat.name, context) - } - - private fun distributionEntries(stat: Stat, context: Context): BarDataSet { - - val colors = mutableListOf() - val entries = mutableListOf() - this._evolutionValues[stat]?.let { points -> - - val negative = mutableListOf() - val positive = mutableListOf() - - points.sortByDescending { it.y } - points.forEach { - if (it.y < 0) { - negative.add(it) - } else { - positive.add(it) - } - } - - negative.forEachIndexed { index, p -> - entries.add(BarEntry(index.toFloat(), abs(p.y.toFloat()), p.data)) - colors.add(context.getColor(R.color.red)) - } - positive.forEachIndexed { index, p -> - val x = negative.size + index.toFloat() - entries.add(BarEntry(x, p.y.toFloat(), p.data)) - colors.add(context.getColor(R.color.green)) - } - - } - return DataSetFactory.barDataSetInstance(entries, stat.name, context, colors) - } - val isEmpty: Boolean get() { return this.group.isEmpty @@ -488,10 +281,4 @@ class ComputedResults(group: ComputableGroup, } } -} - -class Point(val x: Double, val y: Double, val data: Any) { - - constructor(y: Double, data: Any) : this(0.0, y, data) - } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/fragment/report/ProgressReportFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/fragment/report/ProgressReportFragment.kt index 3e3d90c8..ed487940 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/fragment/report/ProgressReportFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/fragment/report/ProgressReportFragment.kt @@ -16,6 +16,10 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import net.pokeranalytics.android.R +import net.pokeranalytics.android.calcul.barEntries +import net.pokeranalytics.android.calcul.defaultStatEntries +import net.pokeranalytics.android.calcul.durationEntries +import net.pokeranalytics.android.calcul.lineEntries import net.pokeranalytics.android.calculus.AggregationType import net.pokeranalytics.android.calculus.Calculator import net.pokeranalytics.android.calculus.Report diff --git a/app/src/main/java/net/pokeranalytics/android/ui/graph/GraphExtensions.kt b/app/src/main/java/net/pokeranalytics/android/ui/graph/GraphExtensions.kt index dd2f28da..ff8fe0b3 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/graph/GraphExtensions.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/graph/GraphExtensions.kt @@ -8,15 +8,10 @@ import com.github.mikephil.charting.charts.BarChart import com.github.mikephil.charting.charts.BarLineChartBase import com.github.mikephil.charting.components.XAxis import net.pokeranalytics.android.R +import net.pokeranalytics.android.calculus.AxisFormatting import net.pokeranalytics.android.model.utils.CrashLogging import net.pokeranalytics.android.ui.extensions.px -enum class AxisFormatting { - DEFAULT, - X_DURATION, - Y_DURATION, -} - fun BarLineChartBase<*>.setStyle( small: Boolean, axisFormatting: AxisFormatting = AxisFormatting.DEFAULT, diff --git a/app/src/main/java/net/pokeranalytics/android/ui/modules/calendar/CalendarDetailsFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/modules/calendar/CalendarDetailsFragment.kt index 336b0c1e..20fef5d8 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/modules/calendar/CalendarDetailsFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/modules/calendar/CalendarDetailsFragment.kt @@ -18,6 +18,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import net.pokeranalytics.android.R +import net.pokeranalytics.android.calcul.defaultStatEntries import net.pokeranalytics.android.calculus.Calculator import net.pokeranalytics.android.calculus.Stat import net.pokeranalytics.android.model.filter.Query diff --git a/app/src/main/java/net/pokeranalytics/android/ui/view/RowViewType.kt b/app/src/main/java/net/pokeranalytics/android/ui/view/RowViewType.kt index e1e049cd..36d655de 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/view/RowViewType.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/view/RowViewType.kt @@ -18,6 +18,7 @@ import com.google.android.material.chip.ChipGroup import kotlinx.android.synthetic.main.row_feed_session.view.* import kotlinx.android.synthetic.main.row_transaction.view.* import net.pokeranalytics.android.R +import net.pokeranalytics.android.calculus.AxisFormatting import net.pokeranalytics.android.calculus.ComputedStat import net.pokeranalytics.android.calculus.Stat import net.pokeranalytics.android.calculus.bankroll.BankrollReportManager @@ -33,7 +34,6 @@ import net.pokeranalytics.android.ui.extensions.ChipGroupExtension import net.pokeranalytics.android.ui.extensions.px import net.pokeranalytics.android.ui.extensions.setTextFormat import net.pokeranalytics.android.ui.modules.bankroll.BankrollRowRepresentable -import net.pokeranalytics.android.ui.graph.AxisFormatting import net.pokeranalytics.android.ui.graph.setStyle import net.pokeranalytics.android.ui.modules.handhistory.views.RowHandHistoryViewHolder import net.pokeranalytics.android.ui.view.holder.RowViewHolder diff --git a/app/src/main/java/net/pokeranalytics/android/ui/viewmodel/GraphViewModel.kt b/app/src/main/java/net/pokeranalytics/android/ui/viewmodel/GraphViewModel.kt index bd72f14c..2c8d09ae 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/viewmodel/GraphViewModel.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/viewmodel/GraphViewModel.kt @@ -4,10 +4,10 @@ import android.content.Context import androidx.lifecycle.ViewModel import com.github.mikephil.charting.data.BarDataSet import com.github.mikephil.charting.data.LineDataSet +import net.pokeranalytics.android.calculus.AxisFormatting import net.pokeranalytics.android.calculus.Stat import net.pokeranalytics.android.exceptions.PAIllegalStateException import net.pokeranalytics.android.ui.fragment.GraphFragment -import net.pokeranalytics.android.ui.graph.AxisFormatting open class GraphViewModel : ViewModel(), GraphDataProvider { diff --git a/app/src/main/java/net/pokeranalytics/android/ui/viewmodel/ReportHolder.kt b/app/src/main/java/net/pokeranalytics/android/ui/viewmodel/ReportHolder.kt index 9b107df3..97d1f57d 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/viewmodel/ReportHolder.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/viewmodel/ReportHolder.kt @@ -3,10 +3,9 @@ package net.pokeranalytics.android.ui.viewmodel import android.content.Context import com.github.mikephil.charting.data.BarDataSet import com.github.mikephil.charting.data.LineDataSet +import net.pokeranalytics.android.calculus.AxisFormatting import net.pokeranalytics.android.calculus.Report import net.pokeranalytics.android.calculus.Stat -import net.pokeranalytics.android.ui.fragment.GraphFragment -import net.pokeranalytics.android.ui.graph.AxisFormatting interface ReportHolder { diff --git a/app/src/main/java/net/pokeranalytics/android/ui/viewmodel/ReportViewModel.kt b/app/src/main/java/net/pokeranalytics/android/ui/viewmodel/ReportViewModel.kt index f9dc20c3..db04a640 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/viewmodel/ReportViewModel.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/viewmodel/ReportViewModel.kt @@ -3,6 +3,9 @@ package net.pokeranalytics.android.ui.viewmodel import android.content.Context import com.github.mikephil.charting.data.BarDataSet import com.github.mikephil.charting.data.LineDataSet +import net.pokeranalytics.android.calcul.barEntries +import net.pokeranalytics.android.calcul.lineEntries +import net.pokeranalytics.android.calcul.multiLineEntries import net.pokeranalytics.android.calculus.Calculator import net.pokeranalytics.android.calculus.Report import net.pokeranalytics.android.ui.activity.components.ReportParameters @@ -15,7 +18,7 @@ class ReportViewModel : GraphViewModel(), ReportHolder, GraphDataProvider { companion object { - // Unparcel fails when setting a custom Parcelable object on Entry so we use a static reference to passe objects + // Unparcel fails when setting a custom Parcelable object on Entry so we use a static reference to pass objects private var _parameters: ReportParameters? = null val parameters: ReportParameters?