commit
46adf8c068
Binary file not shown.
Binary file not shown.
@ -1,127 +1,218 @@ |
||||
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 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.Filter |
||||
import net.pokeranalytics.android.model.realm.SessionSet |
||||
|
||||
/** |
||||
* A sessionGroup of computable items identified by a name |
||||
*/ |
||||
class ComputableGroup(name: String, computables: RealmResults<ComputableResult>, sets: RealmResults<SessionSet>, stats: List<Stat>? = null) { |
||||
|
||||
/** |
||||
* The display name of the group |
||||
*/ |
||||
var name: String = name |
||||
|
||||
/** |
||||
* The list of endedSessions to compute |
||||
*/ |
||||
var computables: RealmResults<ComputableResult> = computables |
||||
|
||||
/** |
||||
* The list of endedSessions to compute |
||||
*/ |
||||
var sets: RealmResults<SessionSet> = 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 |
||||
class ComputableGroup(name: String, conditions: List<QueryCondition>, stats: List<Stat>? = null) { |
||||
|
||||
/** |
||||
* The display name of the group |
||||
*/ |
||||
var name: String = name |
||||
|
||||
/** |
||||
* A list of conditions to get |
||||
*/ |
||||
var conditions: List<QueryCondition> = conditions |
||||
|
||||
/** |
||||
* The list of endedSessions to compute |
||||
*/ |
||||
private var _computables: RealmResults<ComputableResult>? = null |
||||
|
||||
/** |
||||
* Retrieves the computables on the relative [realm] filtered with the provided [conditions] |
||||
*/ |
||||
fun computables(realm: Realm): RealmResults<ComputableResult> { |
||||
|
||||
// if computables exists and is valid (previous realm not closed) |
||||
this._computables?.let { |
||||
if (it.isValid) { |
||||
return it |
||||
} |
||||
} |
||||
|
||||
val computables: RealmResults<ComputableResult> = Filter.queryOn(realm, this.conditions) |
||||
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) { |
||||
|
||||
/** |
||||
* The session group used to computed the stats |
||||
*/ |
||||
var group: ComputableGroup = group |
||||
|
||||
// The computed stats of the sessionGroup |
||||
private var _computedStats: MutableMap<Stat, ComputedStat> = mutableMapOf() |
||||
|
||||
// The map containing all evolution numericValues for all stats |
||||
private var _evolutionValues: MutableMap<Stat, MutableList<Point>> = mutableMapOf() |
||||
|
||||
fun allStats() : Collection<ComputedStat> { |
||||
return this._computedStats.values |
||||
} |
||||
|
||||
fun addEvolutionValue(value: Double, stat: Stat) { |
||||
this._addEvolutionValue(Point(value), stat = stat) |
||||
} |
||||
|
||||
fun addEvolutionValue(value: Double, duration: Double, stat: Stat) { |
||||
this._addEvolutionValue(Point(value, y = duration), stat = stat) |
||||
} |
||||
|
||||
private fun _addEvolutionValue(point: Point, stat: Stat) { |
||||
var evolutionValues = this._evolutionValues[stat] |
||||
if (evolutionValues != null) { |
||||
evolutionValues.add(point) |
||||
} else { |
||||
var values: MutableList<Point> = mutableListOf(point) |
||||
this._evolutionValues[stat] = values |
||||
} |
||||
} |
||||
|
||||
fun addStats(computedStats: Set<ComputedStat>) { |
||||
computedStats.forEach { |
||||
this._computedStats[it.stat] = it |
||||
} |
||||
} |
||||
|
||||
fun computedStat(stat: Stat) : ComputedStat? { |
||||
return this._computedStats[stat] |
||||
} |
||||
|
||||
fun computeStatVariations(resultsToCompare: ComputedResults) { |
||||
this._computedStats.keys.forEach { stat -> |
||||
var 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 { _ -> |
||||
// @todo sort |
||||
/** |
||||
* The session group used to computed the stats |
||||
*/ |
||||
var group: ComputableGroup = group |
||||
|
||||
// The computed stats of the sessionGroup |
||||
private var _computedStats: MutableMap<Stat, ComputedStat> = mutableMapOf() |
||||
|
||||
// The map containing all evolution numericValues for all stats |
||||
private var _evolutionValues: MutableMap<Stat, MutableList<Point>> = mutableMapOf() |
||||
|
||||
fun allStats(): Collection<ComputedStat> { |
||||
return this._computedStats.values |
||||
} |
||||
|
||||
/** |
||||
* Adds a value to the evolution values |
||||
*/ |
||||
fun addEvolutionValue(value: Double, stat: Stat, data: Any) { |
||||
this._addEvolutionValue(Point(value, data), stat = stat) |
||||
} |
||||
|
||||
fun addEvolutionValue(value: Double, duration: Double, stat: Stat, data: Timed) { |
||||
stat.underlyingClass = data::class.java |
||||
this._addEvolutionValue(Point(value, y = duration, data = data.id), stat = stat) |
||||
} |
||||
|
||||
private fun _addEvolutionValue(point: Point, stat: Stat) { |
||||
val evolutionValues = this._evolutionValues[stat] |
||||
if (evolutionValues != null) { |
||||
evolutionValues.add(point) |
||||
} else { |
||||
val values: MutableList<Point> = mutableListOf(point) |
||||
this._evolutionValues[stat] = values |
||||
} |
||||
} |
||||
|
||||
fun addStats(computedStats: Set<ComputedStat>) { |
||||
computedStats.forEach { |
||||
this._computedStats[it.stat] = it |
||||
} |
||||
} |
||||
|
||||
fun computedStat(stat: Stat): ComputedStat? { |
||||
return this._computedStats[stat] |
||||
} |
||||
|
||||
fun computeStatVariations(resultsToCompare: ComputedResults) { |
||||
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 { _ -> |
||||
// @todo sort |
||||
// var evolutionValues = this._evolutionValues[stat] |
||||
// evolutionValues.so |
||||
} |
||||
|
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Returns the number of computed stats |
||||
*/ |
||||
fun numberOfStats() : Int { |
||||
return this._computedStats.size |
||||
} |
||||
} |
||||
|
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Returns the number of computed stats |
||||
*/ |
||||
fun numberOfStats(): Int { |
||||
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) { |
||||
val x: Double = x |
||||
val y: Double = y |
||||
class Point(val x: Double, val y: Double, val data: Any) { |
||||
|
||||
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 |
||||
|
||||
class ModelException(message: String) : Exception(message) { |
||||
|
||||
} |
||||
|
||||
class FormattingException(message: String) : Exception(message) { |
||||
|
||||
} |
||||
|
||||
class RowRepresentableEditDescriptorException(message: String) : Exception(message) { |
||||
|
||||
} |
||||
|
||||
class FilterValueMapException(message: String) : Exception(message) { |
||||
init { |
||||
println("FilterValueMapException(): $message") |
||||
} |
||||
} |
||||
|
||||
class ConfigurationException(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 RowRepresentableEditDescriptorException(message: String) : Exception(message) |
||||
|
||||
class ConfigurationException(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") |
||||
object FilterUnhandledEntity : PokerAnalyticsException(message = "This entity is not filterable") |
||||
object QueryValueMapUnknown: PokerAnalyticsException(message = "fieldName is missing") |
||||
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") |
||||
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