Legend for bar chart and multilines

feature/top10
Laurent 7 years ago
parent 9a5f343626
commit 250c41cde5
  1. 48
      app/src/main/java/net/pokeranalytics/android/calculus/Report.kt
  2. 31
      app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt
  3. 4
      app/src/main/java/net/pokeranalytics/android/model/interfaces/Timed.kt
  4. 61
      app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt
  5. 4
      app/src/main/java/net/pokeranalytics/android/ui/adapter/ReportPagerAdapter.kt
  6. 51
      app/src/main/java/net/pokeranalytics/android/ui/fragment/GraphFragment.kt
  7. 37
      app/src/main/java/net/pokeranalytics/android/ui/graph/GraphUnderlyingEntry.kt
  8. 59
      app/src/main/java/net/pokeranalytics/android/ui/view/LegendView.kt
  9. 32
      app/src/main/java/net/pokeranalytics/android/ui/view/MultiLineLegendView.kt
  10. 0
      app/src/main/res/layout/fragment_graph.xml
  11. 12
      app/src/main/res/layout/layout_legend_color.xml

@ -10,7 +10,10 @@ 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
import net.pokeranalytics.android.ui.fragment.GraphFragment
import net.pokeranalytics.android.ui.graph.GraphUnderlyingEntry
import net.pokeranalytics.android.ui.graph.PALineDataSet
import net.pokeranalytics.android.ui.view.LegendView
import net.pokeranalytics.android.util.ColorUtils
import kotlin.math.abs
@ -176,7 +179,7 @@ class ComputableGroup(name: String = "", conditions: List<QueryCondition> = list
}
class ComputedResults(group: ComputableGroup, shouldManageMultiGroupProgressValues: Boolean = false) : StatEntry {
class ComputedResults(group: ComputableGroup, shouldManageMultiGroupProgressValues: Boolean = false) : GraphUnderlyingEntry {
/**
* The session group used to computed the stats
@ -362,7 +365,7 @@ class ComputedResults(group: ComputableGroup, shouldManageMultiGroupProgressValu
entries.add(Entry(index.toFloat(), p.y.toFloat(), p.data))
}
}
return PALineDataSet(entries, stat.name, context)
return PALineDataSet(entries, this.group.name, context)
}
fun durationEntries(stat: Stat, context: Context): LineDataSet {
@ -442,6 +445,47 @@ class ComputedResults(group: ComputableGroup, shouldManageMultiGroupProgressValu
}
}
override fun legendValues(
stat: Stat,
entry: Entry,
legendType: GraphFragment.LegendType,
groupName: String,
context: Context
): LegendView.Values {
when (legendType) {
GraphFragment.LegendType.DEFAULT_BAR -> {
return when (stat) {
Stat.NUMBER_OF_SETS, Stat.NUMBER_OF_GAMES, Stat.WIN_RATIO -> {
val totalStatValue = stat.format(entry.y.toDouble(), currency = null)
LegendView.Values(this.entryTitle, totalStatValue)
}
else -> {
val entryValue = this.formattedValue(stat)
val countValue = this.computedStat(Stat.NUMBER_OF_GAMES)?.format()
LegendView.Values(this.entryTitle, entryValue, countValue)
}
}
}
else -> {
return when (stat) {
Stat.NUMBER_OF_SETS, Stat.NUMBER_OF_GAMES, Stat.WIN_RATIO -> {
val totalStatValue = stat.format(entry.y.toDouble(), currency = null)
LegendView.Values(this.entryTitle, totalStatValue)
}
else -> {
val entryValue = this.formattedValue(stat)
val totalStatValue = stat.format(entry.y.toDouble(), currency = null)
LegendView.Values(this.entryTitle, entryValue, totalStatValue)
}
}
}
}
}
}
class Point(val x: Double, val y: Double, val data: Any) {

@ -1,12 +1,10 @@
package net.pokeranalytics.android.calculus
import android.content.Context
import com.github.mikephil.charting.data.Entry
import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.FormattingException
import net.pokeranalytics.android.model.interfaces.Timed
import net.pokeranalytics.android.ui.graph.AxisFormatting
import net.pokeranalytics.android.ui.view.LegendView
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
import net.pokeranalytics.android.util.NULL_TEXT
@ -24,35 +22,6 @@ class ObjectIdentifier(var id: String, var clazz: Class<out Timed>) {
}
interface StatEntry {
val entryTitle: String
fun formattedValue(stat: Stat): TextFormat
fun legendValues(stat: Stat, entry: Entry): LegendView.Values {
return when (stat) {
Stat.NUMBER_OF_SETS, Stat.NUMBER_OF_GAMES, Stat.WIN_RATIO -> {
val totalStatValue = stat.format(entry.y.toDouble(), currency = null)
LegendView.Values(this.entryTitle, totalStatValue)
}
else -> {
val entryValue = this.formattedValue(stat)
val totalStatValue = stat.format(entry.y.toDouble(), currency = null)
LegendView.Values(this.entryTitle, entryValue, totalStatValue)
}
}
}
}
enum class GraphType {
LINE,
BAR,
}
enum class AggregationType {
SESSION,
MONTH,

@ -1,10 +1,10 @@
package net.pokeranalytics.android.model.interfaces
import net.pokeranalytics.android.calculus.ObjectIdentifier
import net.pokeranalytics.android.calculus.StatEntry
import net.pokeranalytics.android.ui.graph.GraphUnderlyingEntry
import java.util.*
interface Timed : StatEntry, Identifiable {
interface Timed : GraphUnderlyingEntry, Identifiable {
fun startDate() : Date?

@ -27,6 +27,7 @@ import net.pokeranalytics.android.model.interfaces.*
import net.pokeranalytics.android.model.utils.SessionSetManager
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
import net.pokeranalytics.android.ui.adapter.UnmanagedRowRepresentableException
import net.pokeranalytics.android.ui.fragment.GraphFragment
import net.pokeranalytics.android.ui.view.LegendView
import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowRepresentableEditDescriptor
@ -372,16 +373,6 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
return playerHandsPerHour / tableSize.toDouble()
}
@Ignore
var ratedBuyin: Double = 0.0
get() {
val rate = this.bankroll?.currency?.rate ?: 1.0
this.result?.buyin?.let { buyin ->
return buyin * rate
}
return 0.0
}
val hourlyRate: Double
get() {
this.result?.let { result ->
@ -922,7 +913,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
}
// Stat Base
// Stat Entry
override val entryTitle: String
get() {
@ -969,26 +960,46 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
}
override fun legendValues(stat: Stat, entry: Entry) : LegendView.Values {
override fun legendValues(
stat: Stat,
entry: Entry,
legendType: GraphFragment.LegendType,
groupName: String,
context: Context
) : LegendView.Values {
return when (stat) {
Stat.STANDARD_DEVIATION -> {
val left = this.formattedValue(Stat.NET_RESULT)
when (legendType) {
GraphFragment.LegendType.MULTILINE -> {
val hasMainCurrencyCode = this.bankroll?.currency?.hasMainCurrencyCode() ?: false
var right: TextFormat? = null
val secondTitle = stat.localizedTitle(context)
val entryValue = this.formattedValue(stat)
val dateValue = TextFormat(this.entryTitle)
if (!hasMainCurrencyCode) {
this.computableResult?.ratedNet?.let { ratedNet ->
right = Stat.NET_RESULT.format(ratedNet)
}
}
LegendView.Values(this.entryTitle, left, right)
return LegendView.MultiLineValues(groupName, secondTitle, entryValue, dateValue)
}
else -> {
super.legendValues(stat, entry)
return when (stat) {
Stat.STANDARD_DEVIATION -> {
val left = this.formattedValue(Stat.NET_RESULT)
val hasMainCurrencyCode = this.bankroll?.currency?.hasMainCurrencyCode() ?: false
var right: TextFormat? = null
if (!hasMainCurrencyCode) {
this.computableResult?.ratedNet?.let { ratedNet ->
right = Stat.NET_RESULT.format(ratedNet)
}
}
LegendView.Values(this.entryTitle, left, right)
}
else -> {
super.legendValues(stat, entry, legendType, groupName, context)
}
}
}
}
}

@ -24,11 +24,11 @@ class ReportPagerAdapter(val context: Context, val fragmentManager: FragmentMana
return when (position) {
0 -> {
val dataSetList = listOf(report.barEntries(null, context))
GraphFragment.newInstance(barDataSets = dataSetList)
GraphFragment.newInstance(barDataSets = dataSetList, legendType = GraphFragment.LegendType.DEFAULT_BAR)
}
1 -> {
val dataSetList = report.multiLineEntries(context = context)
GraphFragment.newInstance(lineDataSets = dataSetList)
GraphFragment.newInstance(lineDataSets = dataSetList, legendType = GraphFragment.LegendType.MULTILINE)
}
2 -> {
TableReportFragment.newInstance(report)

@ -10,28 +10,36 @@ import com.github.mikephil.charting.charts.LineChart
import com.github.mikephil.charting.data.*
import com.github.mikephil.charting.highlight.Highlight
import com.github.mikephil.charting.listener.OnChartValueSelectedListener
import kotlinx.android.synthetic.main.fragment_evograph.*
import kotlinx.android.synthetic.main.fragment_graph.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.ObjectIdentifier
import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.calculus.StatEntry
import net.pokeranalytics.android.ui.activity.components.PokerAnalyticsActivity
import net.pokeranalytics.android.ui.fragment.components.PokerAnalyticsFragment
import net.pokeranalytics.android.ui.graph.AxisFormatting
import net.pokeranalytics.android.ui.graph.GraphUnderlyingEntry
import net.pokeranalytics.android.ui.graph.setStyle
import net.pokeranalytics.android.ui.view.LegendView
import net.pokeranalytics.android.ui.view.MultiLineLegendView
import timber.log.Timber
class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener {
enum class LegendType {
DEFAULT,
DEFAULT_BAR,
MULTILINE,
}
companion object {
/**
* Create new instance
*/
fun newInstance(lineDataSets: List<LineDataSet>? = null, barDataSets: List<BarDataSet>? = null): GraphFragment {
fun newInstance(lineDataSets: List<LineDataSet>? = null, barDataSets: List<BarDataSet>? = null, legendType: LegendType = LegendType.DEFAULT): GraphFragment {
val fragment = GraphFragment()
fragment.legendType = legendType
fragment.lineDataSetList = lineDataSets
fragment.barDataSetList = barDataSets
val bundle = Bundle()
@ -42,6 +50,8 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener {
}
private lateinit var parentActivity: PokerAnalyticsActivity
private var legendType: LegendType = LegendType.DEFAULT
private lateinit var legendView: LegendView
private var lineDataSetList: List<LineDataSet>? = null
@ -53,7 +63,7 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener {
private var axisFormatting: AxisFormatting = AxisFormatting.DEFAULT
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_evograph, container, false)
return inflater.inflate(R.layout.fragment_graph, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -93,9 +103,12 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener {
parentActivity = activity as PokerAnalyticsActivity
parentActivity.title = stat.localizedTitle(requireContext())
this.legendView = LegendView(requireContext())
this.legendContainer.addView(this.legendView)
this.legendView = when (this.legendType) {
LegendType.MULTILINE -> MultiLineLegendView(requireContext())
else -> LegendView(requireContext())
}
this.legendContainer.addView(this.legendView)
}
/**
@ -108,6 +121,7 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener {
this.chartContainer.removeAllViews()
var lastEntry: Entry? = null
var groupName: String? = null
this.lineDataSetList?.let { dataSets ->
@ -118,15 +132,16 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener {
this.chartView = lineChart
dataSets.firstOrNull()?.let {
this.legendView.prepareWithStat(this.stat, it.entryCount)
this.legendView.prepareWithStat(this.stat, it.entryCount, this.legendType)
lastEntry = it.getEntryForIndex(it.entryCount - 1)
groupName = it.label
}
}
this.barDataSetList?.let { dataSets ->
this.legendView.prepareWithStat(this.stat)
this.legendView.prepareWithStat(this.stat, legendType = this.legendType)
val barChart = BarChart(context)
if (stat.showXAxisZero) {
barChart.xAxis.axisMinimum = 0.0f
@ -141,6 +156,7 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener {
dataSets.firstOrNull()?.let {
lastEntry = it.getEntryForIndex(it.entryCount - 1)
groupName = it.label
}
}
@ -152,7 +168,7 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener {
}
lastEntry?.let {
this.selectValue(it)
this.selectValue(it, groupName ?: "")
}
}
@ -163,23 +179,32 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener {
}
override fun onValueSelected(e: Entry?, h: Highlight?) {
var groupName = ""
h?.let { highlight ->
this.chartView?.data?.getDataSetByIndex(highlight.dataSetIndex)?.let {
groupName = it.label
}
}
e?.let { entry ->
this.selectValue(entry)
this.selectValue(entry, groupName)
}
}
private fun selectValue(entry: Entry) {
private fun selectValue(entry: Entry, groupName: String) {
val statEntry = when (entry.data) {
is ObjectIdentifier -> {
val identifier = entry.data as ObjectIdentifier
getRealm().where(identifier.clazz).equalTo("id", identifier.id).findAll().firstOrNull()
}
is StatEntry -> entry.data as StatEntry?
is GraphUnderlyingEntry -> entry.data as GraphUnderlyingEntry?
else -> null
}
statEntry?.let {
this.legendView.setItemData(it.legendValues(stat, entry))
val legendValue = it.legendValues(stat, entry, this.legendType, groupName, requireContext())
this.legendView.setItemData(legendValue)
}
}

@ -0,0 +1,37 @@
package net.pokeranalytics.android.ui.graph
import android.content.Context
import com.github.mikephil.charting.data.Entry
import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.calculus.TextFormat
import net.pokeranalytics.android.ui.fragment.GraphFragment
import net.pokeranalytics.android.ui.view.LegendView
interface GraphUnderlyingEntry {
val entryTitle: String
fun formattedValue(stat: Stat): TextFormat
fun legendValues(
stat: Stat,
entry: Entry,
legendType: GraphFragment.LegendType,
groupName: String,
context: Context
): LegendView.Values {
return when (stat) {
Stat.NUMBER_OF_SETS, Stat.NUMBER_OF_GAMES, Stat.WIN_RATIO -> {
val totalStatValue = stat.format(entry.y.toDouble(), currency = null)
LegendView.Values(this.entryTitle, totalStatValue)
}
else -> {
val entryValue = this.formattedValue(stat)
val totalStatValue = stat.format(entry.y.toDouble(), currency = null)
LegendView.Values(this.entryTitle, entryValue, totalStatValue)
}
}
}
}

@ -11,13 +11,15 @@ import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.calculus.TextFormat
import net.pokeranalytics.android.ui.extensions.setTextFormat
import net.pokeranalytics.android.ui.fragment.GraphFragment
/**
* Display a row session
*/
class LegendView : FrameLayout {
open class LegendView : FrameLayout {
class Values(var title: String, var leftFormat: TextFormat, var rightFormat: TextFormat? = null)
open class Values(var title: String, var leftFormat: TextFormat, var rightFormat: TextFormat? = null)
class MultiLineValues(var firstTitle: String, var secondTitle: String, leftFormat: TextFormat, rightFormat: TextFormat? = null) : Values("", leftFormat, rightFormat)
private lateinit var legendLayout: ConstraintLayout
@ -36,12 +38,16 @@ class LegendView : FrameLayout {
init()
}
open protected fun getResourceLayout() : Int {
return R.layout.layout_legend_default
}
/**
* Init
*/
private fun init() {
val layoutInflater = LayoutInflater.from(context)
legendLayout = layoutInflater.inflate(R.layout.layout_legend_default, this, false) as ConstraintLayout
legendLayout = layoutInflater.inflate(this.getResourceLayout(), this, false) as ConstraintLayout
val layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
addView(legendLayout, layoutParams)
}
@ -49,30 +55,39 @@ class LegendView : FrameLayout {
/**
* Set the stat data to the view
*/
fun prepareWithStat(stat: Stat, counter: Int? = null) {
if (stat.significantIndividualValue) {
this.stat1Name.text = stat.localizedTitle(context)
this.stat2Name.text = stat.cumulativeLabelResId(context)
} else {
this.stat1Name.text = stat.cumulativeLabelResId(context)
this.stat2Name.isVisible = false
this.stat2Value.isVisible = false
}
counter?.let {
val counterText = "$it ${context.getString(R.string.sessions)}"
this.counter.text = counterText
this.counter.isVisible = stat.shouldShowNumberOfSessions
open fun prepareWithStat(stat: Stat, counter: Int? = null, legendType: GraphFragment.LegendType) {
when (legendType) {
GraphFragment.LegendType.DEFAULT_BAR -> {
this.stat1Name.text = stat.localizedTitle(context)
this.stat2Name.text = context.getString(R.string.sessions)
this.counter.isVisible = false
}
GraphFragment.LegendType.DEFAULT -> {
if (stat.significantIndividualValue) {
this.stat1Name.text = stat.localizedTitle(context)
this.stat2Name.text = stat.cumulativeLabelResId(context)
} else {
this.stat1Name.text = stat.cumulativeLabelResId(context)
this.stat2Name.isVisible = false
}
counter?.let {
val counterText = "$it ${context.getString(R.string.sessions)}"
this.counter.text = counterText
this.counter.isVisible = stat.shouldShowNumberOfSessions
}
}
else -> {
}
}
}
/**
*
*/
fun setItemData(values: Values) {
open fun setItemData(values: Values) {
this.title.text = values.title
@ -81,10 +96,6 @@ class LegendView : FrameLayout {
this.stat2Value.setTextFormat(it, context)
}
// val showRightStat = values.rightFormat != null
// this.stat2Name.isVisible = showRightStat
// this.stat2Value.isVisible = showRightStat
}
}

@ -0,0 +1,32 @@
package net.pokeranalytics.android.ui.view
import android.content.Context
import kotlinx.android.synthetic.main.layout_legend_default.view.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.ui.extensions.setTextFormat
import net.pokeranalytics.android.ui.fragment.GraphFragment
class MultiLineLegendView(context: Context) : LegendView(context = context) {
override fun getResourceLayout(): Int {
return R.layout.layout_legend_color
}
override fun prepareWithStat(stat: Stat, counter: Int?, legendType: GraphFragment.LegendType) {
}
override fun setItemData(values: Values) {
if (values is MultiLineValues) {
this.stat1Name.text = values.firstTitle
this.stat2Name.text = values.secondTitle
this.stat1Value.setTextFormat(values.leftFormat, context)
this.stat2Value.text = values.title
}
}
}

@ -17,7 +17,7 @@
app:layout_constraintTop_toTopOf="parent" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/title"
android:id="@+id/stat2Value"
style="@style/PokerAnalyticsTheme.TextView.LegendTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -28,7 +28,7 @@
tools:text="11/04/2019" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/statName1"
android:id="@+id/stat1Name"
style="@style/PokerAnalyticsTheme.TextView.LegendStatsTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -39,18 +39,18 @@
tools:text="$ 2/4" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/statName2"
android:id="@+id/stat2Name"
style="@style/PokerAnalyticsTheme.TextView.LegendStatsTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/statName1"
app:layout_constraintTop_toBottomOf="@+id/stat1Name"
tools:text="NET RESULT" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/statValue"
android:id="@+id/stat1Value"
style="@style/PokerAnalyticsTheme.TextView.LegendStatsValue"
android:layout_width="0dp"
android:layout_height="wrap_content"
@ -58,7 +58,7 @@
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/statName2"
app:layout_constraintTop_toBottomOf="@id/stat2Name"
tools:text="$2000" />
<View

Loading…
Cancel
Save