commit
46adf8c068
Binary file not shown.
Binary file not shown.
@ -1,127 +1,218 @@ |
|||||||
package net.pokeranalytics.android.calculus |
package net.pokeranalytics.android.calculus |
||||||
|
|
||||||
|
import com.github.mikephil.charting.data.BarEntry |
||||||
|
import com.github.mikephil.charting.data.Entry |
||||||
|
import io.realm.Realm |
||||||
import io.realm.RealmResults |
import io.realm.RealmResults |
||||||
|
import net.pokeranalytics.android.model.filter.QueryCondition |
||||||
|
import net.pokeranalytics.android.model.interfaces.Timed |
||||||
import net.pokeranalytics.android.model.realm.ComputableResult |
import net.pokeranalytics.android.model.realm.ComputableResult |
||||||
|
import net.pokeranalytics.android.model.realm.Filter |
||||||
import net.pokeranalytics.android.model.realm.SessionSet |
import net.pokeranalytics.android.model.realm.SessionSet |
||||||
|
|
||||||
/** |
/** |
||||||
* A sessionGroup of computable items identified by a name |
* A sessionGroup of computable items identified by a name |
||||||
*/ |
*/ |
||||||
class ComputableGroup(name: String, computables: RealmResults<ComputableResult>, sets: RealmResults<SessionSet>, stats: List<Stat>? = null) { |
class ComputableGroup(name: String, conditions: List<QueryCondition>, stats: List<Stat>? = null) { |
||||||
|
|
||||||
/** |
/** |
||||||
* The display name of the group |
* The display name of the group |
||||||
*/ |
*/ |
||||||
var name: String = name |
var name: String = name |
||||||
|
|
||||||
/** |
/** |
||||||
* The list of endedSessions to compute |
* A list of conditions to get |
||||||
*/ |
*/ |
||||||
var computables: RealmResults<ComputableResult> = computables |
var conditions: List<QueryCondition> = conditions |
||||||
|
|
||||||
/** |
/** |
||||||
* The list of endedSessions to compute |
* The list of endedSessions to compute |
||||||
*/ |
*/ |
||||||
var sets: RealmResults<SessionSet> = sets |
private var _computables: RealmResults<ComputableResult>? = null |
||||||
|
|
||||||
/** |
/** |
||||||
* The list of stats to display |
* Retrieves the computables on the relative [realm] filtered with the provided [conditions] |
||||||
*/ |
*/ |
||||||
var stats: List<Stat>? = stats |
fun computables(realm: Realm): RealmResults<ComputableResult> { |
||||||
|
|
||||||
/** |
// if computables exists and is valid (previous realm not closed) |
||||||
* A subgroup used to compute stat variation |
this._computables?.let { |
||||||
*/ |
if (it.isValid) { |
||||||
var comparedComputables: ComputableGroup? = null |
return it |
||||||
|
} |
||||||
/** |
} |
||||||
* The computed stats of the comparable sessionGroup |
|
||||||
*/ |
val computables: RealmResults<ComputableResult> = Filter.queryOn(realm, this.conditions) |
||||||
var comparedComputedResults: ComputedResults? = null |
this._computables = computables |
||||||
|
return computables |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* The list of sets to compute |
||||||
|
*/ |
||||||
|
private var _sessionSets: RealmResults<SessionSet>? = null |
||||||
|
|
||||||
|
/** |
||||||
|
* Retrieves the session sets on the relative [realm] filtered with the provided [conditions] |
||||||
|
*/ |
||||||
|
fun sessionSets(realm: Realm): RealmResults<SessionSet> { |
||||||
|
// if computables exists and is valid (previous realm not closed) |
||||||
|
this._sessionSets?.let { |
||||||
|
if (it.isValid) { |
||||||
|
return it |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
val sets: RealmResults<SessionSet> = Filter.queryOn(realm, this.conditions) |
||||||
|
this._sessionSets = sets |
||||||
|
return sets |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* The list of stats to display |
||||||
|
*/ |
||||||
|
var stats: List<Stat>? = stats |
||||||
|
|
||||||
|
/** |
||||||
|
* A subgroup used to compute stat variation |
||||||
|
*/ |
||||||
|
var comparedComputables: ComputableGroup? = null |
||||||
|
|
||||||
|
/** |
||||||
|
* The computed stats of the comparable sessionGroup |
||||||
|
*/ |
||||||
|
var comparedComputedResults: ComputedResults? = null |
||||||
|
|
||||||
|
fun cleanup() { |
||||||
|
this._computables = null |
||||||
|
this._sessionSets = null |
||||||
|
} |
||||||
|
|
||||||
} |
} |
||||||
|
|
||||||
class ComputedResults(group: ComputableGroup) { |
class ComputedResults(group: ComputableGroup) { |
||||||
|
|
||||||
/** |
/** |
||||||
* The session group used to computed the stats |
* The session group used to computed the stats |
||||||
*/ |
*/ |
||||||
var group: ComputableGroup = group |
var group: ComputableGroup = group |
||||||
|
|
||||||
// The computed stats of the sessionGroup |
// The computed stats of the sessionGroup |
||||||
private var _computedStats: MutableMap<Stat, ComputedStat> = mutableMapOf() |
private var _computedStats: MutableMap<Stat, ComputedStat> = mutableMapOf() |
||||||
|
|
||||||
// The map containing all evolution numericValues for all stats |
// The map containing all evolution numericValues for all stats |
||||||
private var _evolutionValues: MutableMap<Stat, MutableList<Point>> = mutableMapOf() |
private var _evolutionValues: MutableMap<Stat, MutableList<Point>> = mutableMapOf() |
||||||
|
|
||||||
fun allStats() : Collection<ComputedStat> { |
fun allStats(): Collection<ComputedStat> { |
||||||
return this._computedStats.values |
return this._computedStats.values |
||||||
} |
} |
||||||
|
|
||||||
fun addEvolutionValue(value: Double, stat: Stat) { |
/** |
||||||
this._addEvolutionValue(Point(value), stat = stat) |
* Adds a value to the evolution values |
||||||
} |
*/ |
||||||
|
fun addEvolutionValue(value: Double, stat: Stat, data: Any) { |
||||||
fun addEvolutionValue(value: Double, duration: Double, stat: Stat) { |
this._addEvolutionValue(Point(value, data), stat = stat) |
||||||
this._addEvolutionValue(Point(value, y = duration), stat = stat) |
} |
||||||
} |
|
||||||
|
fun addEvolutionValue(value: Double, duration: Double, stat: Stat, data: Timed) { |
||||||
private fun _addEvolutionValue(point: Point, stat: Stat) { |
stat.underlyingClass = data::class.java |
||||||
var evolutionValues = this._evolutionValues[stat] |
this._addEvolutionValue(Point(value, y = duration, data = data.id), stat = stat) |
||||||
if (evolutionValues != null) { |
} |
||||||
evolutionValues.add(point) |
|
||||||
} else { |
private fun _addEvolutionValue(point: Point, stat: Stat) { |
||||||
var values: MutableList<Point> = mutableListOf(point) |
val evolutionValues = this._evolutionValues[stat] |
||||||
this._evolutionValues[stat] = values |
if (evolutionValues != null) { |
||||||
} |
evolutionValues.add(point) |
||||||
} |
} else { |
||||||
|
val values: MutableList<Point> = mutableListOf(point) |
||||||
fun addStats(computedStats: Set<ComputedStat>) { |
this._evolutionValues[stat] = values |
||||||
computedStats.forEach { |
} |
||||||
this._computedStats[it.stat] = it |
} |
||||||
} |
|
||||||
} |
fun addStats(computedStats: Set<ComputedStat>) { |
||||||
|
computedStats.forEach { |
||||||
fun computedStat(stat: Stat) : ComputedStat? { |
this._computedStats[it.stat] = it |
||||||
return this._computedStats[stat] |
} |
||||||
} |
} |
||||||
|
|
||||||
fun computeStatVariations(resultsToCompare: ComputedResults) { |
fun computedStat(stat: Stat): ComputedStat? { |
||||||
this._computedStats.keys.forEach { stat -> |
return this._computedStats[stat] |
||||||
var computedStat = this.computedStat(stat) |
} |
||||||
val comparedStat = resultsToCompare.computedStat(stat) |
|
||||||
if (computedStat != null && comparedStat != null) { |
fun computeStatVariations(resultsToCompare: ComputedResults) { |
||||||
computedStat.variation = (computedStat.value - comparedStat.value) / comparedStat.value |
this._computedStats.keys.forEach { stat -> |
||||||
} |
val computedStat = this.computedStat(stat) |
||||||
} |
val comparedStat = resultsToCompare.computedStat(stat) |
||||||
} |
if (computedStat != null && comparedStat != null) { |
||||||
|
computedStat.variation = (computedStat.value - comparedStat.value) / comparedStat.value |
||||||
fun finalize(options: Calculator.Options) { |
} |
||||||
if (options.evolutionValues != Calculator.Options.EvolutionValues.NONE) { |
} |
||||||
|
} |
||||||
// Sort points as a distribution |
|
||||||
this._computedStats.keys.filter { it.hasDistributionSorting() }.forEach { _ -> |
fun finalize(options: Calculator.Options) { |
||||||
// @todo sort |
if (options.evolutionValues != Calculator.Options.EvolutionValues.NONE) { |
||||||
|
|
||||||
|
// Sort points as a distribution |
||||||
|
this._computedStats.keys.filter { it.hasDistributionSorting() }.forEach { _ -> |
||||||
|
// @todo sort |
||||||
// var evolutionValues = this._evolutionValues[stat] |
// var evolutionValues = this._evolutionValues[stat] |
||||||
// evolutionValues.so |
// evolutionValues.so |
||||||
} |
} |
||||||
|
|
||||||
} |
} |
||||||
} |
} |
||||||
|
|
||||||
/** |
/** |
||||||
* Returns the number of computed stats |
* Returns the number of computed stats |
||||||
*/ |
*/ |
||||||
fun numberOfStats() : Int { |
fun numberOfStats(): Int { |
||||||
return this._computedStats.size |
return this._computedStats.size |
||||||
} |
} |
||||||
|
|
||||||
|
// MPAndroidChart |
||||||
|
|
||||||
|
fun defaultStatEntries(stat: Stat): List<Entry> { |
||||||
|
return when (stat) { |
||||||
|
Stat.NUMBER_OF_SETS, Stat.NUMBER_OF_GAMES -> this.barEntries(stat) |
||||||
|
else -> this.singleLineEntries(stat) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun singleLineEntries(stat: Stat): List<Entry> { |
||||||
|
val entries = mutableListOf<Entry>() |
||||||
|
this._evolutionValues[stat]?.let { points -> |
||||||
|
points.forEachIndexed { index, p -> |
||||||
|
entries.add(Entry(index.toFloat(), p.y.toFloat(), p.data)) |
||||||
|
} |
||||||
|
} |
||||||
|
return entries |
||||||
|
} |
||||||
|
|
||||||
|
fun durationEntries(stat: Stat): List<Entry> { |
||||||
|
val entries = mutableListOf<Entry>() |
||||||
|
this._evolutionValues[stat]?.let { points -> |
||||||
|
points.forEach { p -> |
||||||
|
entries.add(Entry(p.x.toFloat(), p.y.toFloat(), p.data)) |
||||||
|
} |
||||||
|
} |
||||||
|
return entries |
||||||
|
} |
||||||
|
|
||||||
|
fun barEntries(stat: Stat): List<BarEntry> { |
||||||
|
|
||||||
|
val entries = mutableListOf<BarEntry>() |
||||||
|
this._evolutionValues[stat]?.let { points -> |
||||||
|
points.forEach { p -> |
||||||
|
entries.add(BarEntry(p.x.toFloat(), p.y.toFloat(), p.data)) |
||||||
|
} |
||||||
|
} |
||||||
|
return entries |
||||||
|
} |
||||||
|
|
||||||
} |
} |
||||||
|
|
||||||
class Point(x: Double, y: Double) { |
class Point(val x: Double, val y: Double, val data: Any) { |
||||||
val x: Double = x |
|
||||||
val y: Double = y |
|
||||||
|
|
||||||
constructor(x: Double) : this(x, 1.0) |
constructor(y: Double, data: Any) : this(0.0, y, data) |
||||||
|
|
||||||
} |
} |
||||||
@ -1,23 +1,23 @@ |
|||||||
package net.pokeranalytics.android.exceptions |
package net.pokeranalytics.android.exceptions |
||||||
|
|
||||||
class ModelException(message: String) : Exception(message) { |
import net.pokeranalytics.android.ui.view.rowrepresentable.FilterElementRow |
||||||
|
|
||||||
} |
class ModelException(message: String) : Exception(message) |
||||||
|
class FormattingException(message: String) : Exception(message) |
||||||
class FormattingException(message: String) : Exception(message) { |
class RowRepresentableEditDescriptorException(message: String) : Exception(message) |
||||||
|
|
||||||
} |
class ConfigurationException(message: String) : Exception(message) |
||||||
|
|
||||||
class RowRepresentableEditDescriptorException(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 FilterElementUnknownSectionName: PokerAnalyticsException(message = "No filterElement section name was found to identify the queryCondition") |
||||||
|
object FilterMissingEntity: PokerAnalyticsException(message = "This filter has no entity initialized") |
||||||
class FilterValueMapException(message: String) : Exception(message) { |
object FilterUnhandledEntity : PokerAnalyticsException(message = "This entity is not filterable") |
||||||
init { |
object QueryValueMapUnknown: PokerAnalyticsException(message = "fieldName is missing") |
||||||
println("FilterValueMapException(): $message") |
object QueryTypeUnhandled: PokerAnalyticsException(message = "filter type not handled") |
||||||
} |
object QueryValueMapUnexpectedValue: PokerAnalyticsException(message = "valueMap null not expected") |
||||||
} |
object FilterElementExpectedValueMissing : PokerAnalyticsException(message = "filter is empty or null") |
||||||
|
data class FilterElementTypeMissing(val filterElementRow: FilterElementRow) : PokerAnalyticsException(message = "filter element '$filterElementRow' type is missing") |
||||||
class ConfigurationException(message: String) : Exception(message) { |
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") |
||||||
} |
} |
||||||
@ -0,0 +1,125 @@ |
|||||||
|
package net.pokeranalytics.android.model.realm |
||||||
|
|
||||||
|
import io.realm.RealmList |
||||||
|
import io.realm.RealmObject |
||||||
|
import net.pokeranalytics.android.exceptions.PokerAnalyticsException |
||||||
|
import net.pokeranalytics.android.model.filter.QueryCondition |
||||||
|
import net.pokeranalytics.android.ui.view.rowrepresentable.FilterElementRow |
||||||
|
import net.pokeranalytics.android.ui.view.rowrepresentable.FilterElementRow.* |
||||||
|
import java.util.* |
||||||
|
|
||||||
|
open class FilterCondition() : RealmObject() { |
||||||
|
|
||||||
|
private constructor(filterName:String, sectionName:String) : this() { |
||||||
|
this.filterName = filterName |
||||||
|
this.sectionName = sectionName |
||||||
|
} |
||||||
|
|
||||||
|
constructor(filterElementRows: ArrayList<FilterElementRow>) : this(filterElementRows.first().filterName, filterElementRows.first().filterSectionRow.name) { |
||||||
|
val row = filterElementRows.first() |
||||||
|
this.filterName ?: throw PokerAnalyticsException.FilterElementUnknownName |
||||||
|
|
||||||
|
when (row) { |
||||||
|
is DateFilterElementRow -> { |
||||||
|
this.dateValue = row.dateValue |
||||||
|
} |
||||||
|
is StringFilterElementRow -> { |
||||||
|
this.stringValues = RealmList<String>().apply { |
||||||
|
this.addAll(filterElementRows.map { |
||||||
|
(it as StringFilterElementRow).stringValue |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
is NumericFilterElementRow -> { |
||||||
|
this.numericValues = RealmList<Double>().apply { |
||||||
|
this.addAll(filterElementRows.map { |
||||||
|
(it as NumericFilterElementRow).doubleValue |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
is FilterElementBlind -> { |
||||||
|
this.blindValues = RealmList<FilterElementBlind>().apply { |
||||||
|
this.addAll(filterElementRows.map { |
||||||
|
FilterElementBlind((it as FilterElementRow.Blind).sb, it.bb, it.code) |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
var filterName: String? = null |
||||||
|
var sectionName: String? = null |
||||||
|
|
||||||
|
val queryCondition : QueryCondition |
||||||
|
get() = QueryCondition.valueOf(this.filterName ?: throw PokerAnalyticsException.FilterElementUnknownName) |
||||||
|
.apply { |
||||||
|
this.updateValueMap(this@FilterCondition) |
||||||
|
} |
||||||
|
|
||||||
|
private var numericValues: RealmList<Double>? = null |
||||||
|
private var dateValue: Date? = null |
||||||
|
private var stringValues: RealmList<String>? = null |
||||||
|
private var blindValues: RealmList<FilterElementBlind>? = null |
||||||
|
|
||||||
|
val ids: Array<String> |
||||||
|
get() = stringValues?.toTypedArray() ?: throw PokerAnalyticsException.FilterElementExpectedValueMissing |
||||||
|
|
||||||
|
val blinds: RealmList<FilterElementBlind> |
||||||
|
get() { |
||||||
|
blindValues?.let { |
||||||
|
if (it.isNotEmpty()) { |
||||||
|
return it |
||||||
|
} else { |
||||||
|
throw PokerAnalyticsException.FilterElementExpectedValueMissing |
||||||
|
} |
||||||
|
} |
||||||
|
throw PokerAnalyticsException.FilterElementExpectedValueMissing |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
val date: Date |
||||||
|
get() = dateValue ?: throw PokerAnalyticsException.FilterElementExpectedValueMissing |
||||||
|
|
||||||
|
|
||||||
|
val values: Array<Int> |
||||||
|
get() = numericValues?.map { |
||||||
|
it.toInt() |
||||||
|
}?.toTypedArray() ?: throw PokerAnalyticsException.FilterElementExpectedValueMissing |
||||||
|
|
||||||
|
|
||||||
|
val value: Double |
||||||
|
get() = numericValues?.first() ?: throw PokerAnalyticsException.FilterElementExpectedValueMissing |
||||||
|
|
||||||
|
|
||||||
|
val leftValue: Double |
||||||
|
get() = numericValues?.first() ?: throw PokerAnalyticsException.FilterElementExpectedValueMissing |
||||||
|
|
||||||
|
|
||||||
|
val rightValue: Double |
||||||
|
get() = numericValues?.last() ?: throw PokerAnalyticsException.FilterElementExpectedValueMissing |
||||||
|
|
||||||
|
|
||||||
|
val dayOfWeek: Int |
||||||
|
get() = numericValues?.first()?.toInt() ?: throw PokerAnalyticsException.FilterElementExpectedValueMissing |
||||||
|
|
||||||
|
|
||||||
|
val month: Int |
||||||
|
get() = numericValues?.first()?.toInt() ?: throw PokerAnalyticsException.FilterElementExpectedValueMissing |
||||||
|
|
||||||
|
|
||||||
|
val year: Int |
||||||
|
get() = numericValues?.first()?.toInt() ?: throw PokerAnalyticsException.FilterElementExpectedValueMissing |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Return the value associated with the given [filterElementRow] |
||||||
|
*/ |
||||||
|
fun getFilterConditionValue(filterElementRow: FilterElementRow): Any? { |
||||||
|
return when (filterElementRow) { |
||||||
|
is From, is To -> dateValue //TODO: Probably change by 'date' (doesn't work now because the value isn't correctly saved |
||||||
|
is PastDays -> values |
||||||
|
else -> throw PokerAnalyticsException.FilterElementTypeMissing(filterElementRow) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -1,144 +0,0 @@ |
|||||||
package net.pokeranalytics.android.model.realm |
|
||||||
|
|
||||||
import io.realm.RealmList |
|
||||||
import io.realm.RealmObject |
|
||||||
import net.pokeranalytics.android.exceptions.FilterValueMapException |
|
||||||
import net.pokeranalytics.android.model.filter.QueryType |
|
||||||
import net.pokeranalytics.android.ui.view.rowrepresentable.FilterElementRow |
|
||||||
import net.pokeranalytics.android.ui.view.rowrepresentable.FilterElementRow.* |
|
||||||
import java.util.* |
|
||||||
import kotlin.collections.ArrayList |
|
||||||
|
|
||||||
open class FilterElement(var filterName : String = "", var sectionName: String = "") : RealmObject() { |
|
||||||
|
|
||||||
constructor(filterElementRows: ArrayList<FilterElementRow>) : this(filterElementRows.first().filterName, filterElementRows.first().filterSectionRow.name) { |
|
||||||
this.stringValues = when (QueryType.valueOf(this.filterName)) { |
|
||||||
QueryType.GAME, QueryType.BANKROLL, QueryType.TOURNAMENT_NAME, QueryType.ALL_TOURNAMENT_FEATURES, QueryType.ANY_TOURNAMENT_FEATURES, QueryType.LOCATION -> { |
|
||||||
RealmList<String>().apply { |
|
||||||
this.addAll(filterElementRows.map { |
|
||||||
(it as DataFilterElementRow).id |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
||||||
else -> null |
|
||||||
} |
|
||||||
|
|
||||||
this.numericValues = when (QueryType.valueOf(filterName)) { |
|
||||||
QueryType.LIMIT -> { |
|
||||||
RealmList<Double>().apply { |
|
||||||
this.addAll(filterElementRows.map { |
|
||||||
(it as FilterElementRow.Limit).limit.ordinal.toDouble() |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
||||||
QueryType.TABLE_SIZE -> { |
|
||||||
RealmList<Double>().apply { |
|
||||||
this.addAll(filterElementRows.map { |
|
||||||
(it as FilterElementRow.TableSize).tableSize.numberOfPlayer.toDouble() |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
||||||
QueryType.YEAR, QueryType.MONTH, QueryType.DAY_OF_WEEK -> { |
|
||||||
RealmList<Double>().apply { |
|
||||||
this.addAll(filterElementRows.map { |
|
||||||
(it as SingleValueFilterElementRow).value.toDouble() |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
||||||
QueryType.LESS_THAN_NET_RESULT -> { |
|
||||||
RealmList<Double>().apply { |
|
||||||
this.addAll(filterElementRows.map { |
|
||||||
(it as ResultLessThan).value |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
||||||
QueryType.MORE_THAN_NET_RESULT -> { |
|
||||||
RealmList<Double>().apply { |
|
||||||
this.addAll(filterElementRows.map { |
|
||||||
(it as ResultMoreThan).value |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
||||||
else -> null |
|
||||||
} |
|
||||||
|
|
||||||
this.blindValues = when (QueryType.valueOf(filterName)) { |
|
||||||
QueryType.BLINDS -> { |
|
||||||
RealmList<FilterElementBlind>().apply { |
|
||||||
this.addAll(filterElementRows.map { |
|
||||||
FilterElementBlind((it as FilterElementRow.Blind).sb, it.bb, it.code) |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
||||||
else -> null |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
constructor(filterElementRow:FilterElementRow) : this(arrayListOf(filterElementRow)) { |
|
||||||
when (filterElementRow) { |
|
||||||
is From -> dateValue = filterElementRow.date |
|
||||||
is To -> dateValue= filterElementRow.date |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
val queryType : QueryType |
|
||||||
get() = QueryType.valueOf(filterName) |
|
||||||
.apply { |
|
||||||
this.updateValueMap(this@FilterElement) |
|
||||||
} |
|
||||||
|
|
||||||
private var numericValues: RealmList<Double>? = null |
|
||||||
private var dateValue : Date? = null |
|
||||||
private var stringValues : RealmList<String>? = null |
|
||||||
private var blindValues : RealmList<FilterElementBlind>? = null |
|
||||||
|
|
||||||
val ids : Array<String> |
|
||||||
get() = stringValues?.toTypedArray()?: throw FilterValueMapException("filter type not handled") |
|
||||||
|
|
||||||
val blinds : RealmList<FilterElementBlind> |
|
||||||
get() { |
|
||||||
blindValues?.let { |
|
||||||
if (it.isNotEmpty()) { |
|
||||||
return it |
|
||||||
} else { |
|
||||||
throw FilterValueMapException("filter is empty or null") |
|
||||||
} |
|
||||||
} |
|
||||||
throw FilterValueMapException("filter is empty or null") |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
val date : Date |
|
||||||
get() = dateValue?: throw FilterValueMapException("filter type not handled") |
|
||||||
|
|
||||||
|
|
||||||
val values : Array<Int> |
|
||||||
get() = numericValues?.map { |
|
||||||
it.toInt() |
|
||||||
}?.toTypedArray()?: throw FilterValueMapException("filter type not handled") |
|
||||||
|
|
||||||
|
|
||||||
val value : Double |
|
||||||
get() = numericValues?.first()?: throw FilterValueMapException("filter type not handled") |
|
||||||
|
|
||||||
|
|
||||||
val leftValue : Double |
|
||||||
get() = numericValues?.first()?: throw FilterValueMapException("filter type not handled") |
|
||||||
|
|
||||||
|
|
||||||
val rightValue : Double |
|
||||||
get() = numericValues?.last()?: throw FilterValueMapException("filter type not handled") |
|
||||||
|
|
||||||
|
|
||||||
val dayOfWeek : Int |
|
||||||
get() = numericValues?.first()?.toInt()?: throw FilterValueMapException("filter type not handled") |
|
||||||
|
|
||||||
|
|
||||||
val month : Int |
|
||||||
get() = numericValues?.first()?.toInt()?: throw FilterValueMapException("filter type not handled") |
|
||||||
|
|
||||||
|
|
||||||
val year : Int |
|
||||||
get() = numericValues?.first()?.toInt()?: throw FilterValueMapException("filter type not handled") |
|
||||||
|
|
||||||
} |
|
||||||
@ -0,0 +1,69 @@ |
|||||||
|
package net.pokeranalytics.android.ui.activity |
||||||
|
|
||||||
|
import android.content.Context |
||||||
|
import android.content.Intent |
||||||
|
import android.os.Bundle |
||||||
|
import com.github.mikephil.charting.data.Entry |
||||||
|
import net.pokeranalytics.android.R |
||||||
|
import net.pokeranalytics.android.calculus.Stat |
||||||
|
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity |
||||||
|
import net.pokeranalytics.android.ui.fragment.GraphFragment |
||||||
|
|
||||||
|
class GraphParameters(stat: Stat, entries: List<Entry>) { |
||||||
|
var stat: Stat = stat |
||||||
|
var entries: List<Entry> = entries |
||||||
|
} |
||||||
|
|
||||||
|
class GraphActivity : PokerAnalyticsActivity() { |
||||||
|
|
||||||
|
private enum class IntentKey(val keyName: String) { |
||||||
|
STAT("STAT"), |
||||||
|
ENTRIES("ENTRIES"), |
||||||
|
} |
||||||
|
|
||||||
|
companion object { |
||||||
|
|
||||||
|
// Unparcel fails when setting a custom Parcelable object on Entry so we use a static reference to passe objects |
||||||
|
var parameters: GraphParameters? = null |
||||||
|
|
||||||
|
/** |
||||||
|
* Default constructor |
||||||
|
*/ |
||||||
|
fun newInstance(context: Context, stat: Stat, entries: List<Entry>) { |
||||||
|
|
||||||
|
GraphActivity.parameters = GraphParameters(stat, entries) |
||||||
|
|
||||||
|
val intent = Intent(context, GraphActivity::class.java) |
||||||
|
context.startActivity(intent) |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) { |
||||||
|
super.onCreate(savedInstanceState) |
||||||
|
setContentView(R.layout.activity_editable_data) |
||||||
|
initUI() |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Init UI |
||||||
|
*/ |
||||||
|
private fun initUI() { |
||||||
|
|
||||||
|
val fragmentManager = supportFragmentManager |
||||||
|
val fragmentTransaction = fragmentManager.beginTransaction() |
||||||
|
val fragment = GraphFragment() |
||||||
|
|
||||||
|
fragmentTransaction.add(R.id.container, fragment) |
||||||
|
fragmentTransaction.commit() |
||||||
|
|
||||||
|
GraphActivity.parameters?.let { |
||||||
|
fragment.setData(it.stat, it.entries) |
||||||
|
GraphActivity.parameters = null |
||||||
|
} ?: run { |
||||||
|
throw Exception("Missing graph parameters") |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,97 @@ |
|||||||
|
package net.pokeranalytics.android.ui.fragment |
||||||
|
|
||||||
|
import android.os.Bundle |
||||||
|
import android.view.LayoutInflater |
||||||
|
import android.view.View |
||||||
|
import android.view.ViewGroup |
||||||
|
import com.github.mikephil.charting.data.Entry |
||||||
|
import com.github.mikephil.charting.data.LineData |
||||||
|
import com.github.mikephil.charting.data.LineDataSet |
||||||
|
import com.github.mikephil.charting.highlight.Highlight |
||||||
|
import com.github.mikephil.charting.listener.OnChartValueSelectedListener |
||||||
|
import kotlinx.android.synthetic.main.fragment_graph.* |
||||||
|
import net.pokeranalytics.android.R |
||||||
|
import net.pokeranalytics.android.calculus.Stat |
||||||
|
import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment |
||||||
|
import net.pokeranalytics.android.ui.graph.setStyle |
||||||
|
|
||||||
|
interface GraphDataSource { |
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener { |
||||||
|
|
||||||
|
lateinit var dataSource: GraphDataSource |
||||||
|
|
||||||
|
lateinit var stat: Stat |
||||||
|
lateinit var entries: List<Entry> |
||||||
|
|
||||||
|
companion object { |
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
fun setData(stat: Stat, entries: List<Entry>) { |
||||||
|
this.stat = stat |
||||||
|
this.entries = entries |
||||||
|
} |
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { |
||||||
|
return inflater.inflate(R.layout.fragment_graph, container, false) |
||||||
|
} |
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
||||||
|
super.onViewCreated(view, savedInstanceState) |
||||||
|
initUI() |
||||||
|
} |
||||||
|
|
||||||
|
private fun initUI() { |
||||||
|
|
||||||
|
val dataSet = LineDataSet(this.entries, this.stat.name) |
||||||
|
val colors = arrayOf(R.color.green_light).toIntArray() |
||||||
|
dataSet.setColors(colors, context) |
||||||
|
val lineData = LineData(listOf(dataSet)) |
||||||
|
|
||||||
|
this.chart.setStyle() |
||||||
|
|
||||||
|
this.chart.data = lineData |
||||||
|
this.chart.setOnChartValueSelectedListener(this) |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
// OnChartValueSelectedListener |
||||||
|
|
||||||
|
override fun onNothingSelected() { |
||||||
|
// nothing to do |
||||||
|
} |
||||||
|
|
||||||
|
override fun onValueSelected(e: Entry?, h: Highlight?) { |
||||||
|
|
||||||
|
e?.let { entry -> |
||||||
|
h?.let { highlight -> |
||||||
|
|
||||||
|
val id = entry.data as String |
||||||
|
val item = getRealm().where(this.stat.underlyingClass).equalTo("id", id).findAll().firstOrNull() |
||||||
|
item?.let { |
||||||
|
|
||||||
|
val date = it.startDate() |
||||||
|
|
||||||
|
val entryStatName = this.stat.localizedTitle(requireContext()) |
||||||
|
val entryValue = it.formattedValue(this.stat, requireContext()) |
||||||
|
|
||||||
|
val totalStatName = this.stat.cumulativeLabelResId(requireContext()) |
||||||
|
val totalStatValue = this.stat.format(e.y.toDouble(), null, requireContext()) |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
this.text.text = "" |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,66 @@ |
|||||||
|
package net.pokeranalytics.android.ui.graph |
||||||
|
|
||||||
|
import com.github.mikephil.charting.charts.BarChart |
||||||
|
import com.github.mikephil.charting.charts.BarLineChartBase |
||||||
|
import com.github.mikephil.charting.charts.LineChart |
||||||
|
|
||||||
|
fun BarChart.setStyle() { |
||||||
|
GraphHelper.setStyle(this) |
||||||
|
} |
||||||
|
|
||||||
|
fun LineChart.setStyle() { |
||||||
|
GraphHelper.setStyle(this) |
||||||
|
} |
||||||
|
|
||||||
|
class GraphHelper { |
||||||
|
|
||||||
|
companion object { |
||||||
|
|
||||||
|
fun setStyle(chart: BarLineChartBase<*>) { |
||||||
|
|
||||||
|
// this.xAxis.axisLineColor = ContextCompat.getColor(context, R.color.) ChartAppearance.defaultColor |
||||||
|
// this.xAxis.axisLineWidth = ChartAppearance.lineWidth |
||||||
|
// this.xAxis.enableGridDashedLine(3.0f, 5.0f, 1.0f) |
||||||
|
// |
||||||
|
// this.xAxis.labelTextColor = ChartAppearance.defaultColor |
||||||
|
// this.xAxis.labelFont = Fonts.graphAxis |
||||||
|
// this.xAxis.labelCount = 4 |
||||||
|
// this.xAxis.labelPosition = .bottom |
||||||
|
// |
||||||
|
// this.xAxis.drawLabelsEnabled = true |
||||||
|
// this.xAxis.drawGridLinesEnabled = true |
||||||
|
// this.xAxis.granularity = 1.0 |
||||||
|
// this.xAxis.granularityEnabled = true |
||||||
|
// this.xAxis.enabled = true |
||||||
|
// |
||||||
|
// // Y Axis |
||||||
|
// this.leftAxis.drawAxisLineEnabled = false |
||||||
|
// this.leftAxis.drawGridLinesEnabled = true |
||||||
|
// this.leftAxis.gridLineDashLengths = [3.0, 5.0] |
||||||
|
// |
||||||
|
// this.leftAxis.drawZeroLineEnabled = true |
||||||
|
// this.leftAxis.zeroLineWidth = ChartAppearance.lineWidth |
||||||
|
// this.leftAxis.zeroLineColor = ChartAppearance.defaultColor |
||||||
|
// |
||||||
|
// this.leftAxis.granularityEnabled = true |
||||||
|
// this.leftAxis.granularity = 1.0 |
||||||
|
// |
||||||
|
// this.leftAxis.labelTextColor = ChartAppearance.defaultColor |
||||||
|
// this.leftAxis.labelFont = Fonts.graphAxis |
||||||
|
// this.leftAxis.labelCount = small ? 1 : 7 // @todo not great if interval is [0..2] for number of records as we get decimals |
||||||
|
// |
||||||
|
// if timeYAxis { |
||||||
|
// this.leftAxis.valueFormatter = HourValueFormatter() |
||||||
|
// } else { |
||||||
|
// this.leftAxis.valueFormatter = LargeNumberFormatter() |
||||||
|
// } |
||||||
|
// |
||||||
|
// this.rightAxis.enabled = false |
||||||
|
// |
||||||
|
// this.legend.enabled = false |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,7 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
android:id="@+id/container" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent"> |
||||||
|
|
||||||
|
</FrameLayout> |
||||||
@ -0,0 +1,25 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout |
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto" |
||||||
|
xmlns:tools="http://schemas.android.com/tools" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent"> |
||||||
|
|
||||||
|
<TextView |
||||||
|
android:id="@+id/text" |
||||||
|
android:textColor="@color/white" |
||||||
|
android:layout_width="wrap_content" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
app:layout_constraintEnd_toEndOf="parent" |
||||||
|
app:layout_constraintStart_toStartOf="parent" |
||||||
|
android:layout_marginStart="8dp" |
||||||
|
app:layout_constraintTop_toTopOf="@+id/chart" |
||||||
|
android:layout_marginTop="8dp"/> |
||||||
|
|
||||||
|
<com.github.mikephil.charting.charts.LineChart |
||||||
|
android:id="@+id/chart" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent"/> |
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout> |
||||||
Loading…
Reference in new issue