Skip to content

Commit

Permalink
feat: add Pie and Combined(bar + line) chart in HealthFragment
Browse files Browse the repository at this point in the history
- Add menu(in toolbar) in HealthFragment for changing chart type
- Add Pie chart for KCal, Time and Distance in HealthFragment
- Add Combined chart for KCal, Time and Distance in HealthFragment
  • Loading branch information
princeraj-pr committed Dec 22, 2023
1 parent 3bf7051 commit 9ebb85d
Show file tree
Hide file tree
Showing 20 changed files with 1,246 additions and 156 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.example.cyclofit.ui.adapter

import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Lifecycle
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.example.cyclofit.ui.fragment.DistanceCombineChartFragment
import com.example.cyclofit.ui.fragment.KcalCombinedChartFragment
import com.example.cyclofit.ui.fragment.TimeCombinedChartFragment

class CombinedChartAdapter(fragmentManager: FragmentManager, lifecycle: Lifecycle) :
FragmentStateAdapter(fragmentManager, lifecycle) {
override fun getItemCount(): Int {
return 3
}

override fun createFragment(position: Int): Fragment {
return when (position) {
0 -> KcalCombinedChartFragment()
1 -> TimeCombinedChartFragment()
else -> DistanceCombineChartFragment()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import com.example.cyclofit.ui.fragment.DistanceFragment
import com.example.cyclofit.ui.fragment.KcalFragment
import com.example.cyclofit.ui.fragment.TimeFragment

class ViewAdapter(fragmentManager: FragmentManager,lifecycle : Lifecycle) : FragmentStateAdapter(fragmentManager,lifecycle) {
class LineChartAdapter(fragmentManager: FragmentManager, lifecycle : Lifecycle) : FragmentStateAdapter(fragmentManager,lifecycle) {
override fun getItemCount(): Int {
return 3
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.example.cyclofit.ui.adapter

import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Lifecycle
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.example.cyclofit.ui.fragment.DistancePieChartFragment
import com.example.cyclofit.ui.fragment.KcalPeiChartFragment
import com.example.cyclofit.ui.fragment.TimePieCharFragment

class PieChartAdapter(fragmentManager: FragmentManager, lifecycle: Lifecycle) :
FragmentStateAdapter(fragmentManager, lifecycle) {
override fun getItemCount(): Int {
return 3
}

override fun createFragment(position: Int): Fragment {
return when (position) {
0 -> KcalPeiChartFragment()
1 -> TimePieCharFragment()
else -> DistancePieChartFragment()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package com.example.cyclofit.ui.fragment

import android.content.Context
import android.graphics.Color
import android.graphics.Typeface
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.res.ResourcesCompat
import androidx.fragment.app.Fragment
import com.example.cyclofit.R
import com.example.cyclofit.databinding.FragmentDistanceCombineChartBinding
import com.example.cyclofit.model.Shared
import com.example.cyclofit.ui.utils.ChartUtils.months
import com.example.cyclofit.ui.utils.Constants
import com.github.mikephil.charting.charts.CombinedChart
import com.github.mikephil.charting.components.AxisBase
import com.github.mikephil.charting.components.Legend
import com.github.mikephil.charting.components.XAxis
import com.github.mikephil.charting.components.YAxis
import com.github.mikephil.charting.data.BarData
import com.github.mikephil.charting.data.BarDataSet
import com.github.mikephil.charting.data.BarEntry
import com.github.mikephil.charting.data.CombinedData
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.formatter.ValueFormatter
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import java.lang.reflect.Type

class DistanceCombineChartFragment : Fragment() {
lateinit var binding: FragmentDistanceCombineChartBinding
var sp = ArrayList<Shared>()

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentDistanceCombineChartBinding.inflate(inflater, container, false)
val sharedPreferences1 = requireContext().getSharedPreferences(
Constants.CYCLOFIT_PREFERENCES,
Context.MODE_PRIVATE
)
val gson = Gson()
val json = sharedPreferences1.getString("timer", null)
val type: Type = object : TypeToken<ArrayList<Shared?>?>() {}.type

if (json != null) {
sp = gson.fromJson(json, type)
}

val distanceArrayList = ArrayList<String>()
for (i in 0 until sp.size) {
distanceArrayList.add("0")
}
val lineDataList = mutableListOf<Entry>()
val barDataList = mutableListOf<BarEntry>()
for ((x, i) in distanceArrayList.withIndex()) {
lineDataList.add(Entry(x.toFloat(), i.toFloat()))
barDataList.add(BarEntry(x.toFloat(), i.toFloat()))
}
val combinedData = CombinedData().apply {
setData(generateLineData(lineDataList))
setData(generateBarData(barDataList))
setValueTypeface(Typeface.DEFAULT)
}
setCombinedChart(combinedData)
return binding.root
}

private fun setCombinedChart(combinedData: CombinedData) {
binding.combinedChart.apply {
data = combinedData
invalidate()
description.isEnabled = false
setBackgroundColor(Color.TRANSPARENT)
setDrawGridBackground(false)
setDrawBarShadow(false)
isHighlightFullBarEnabled = false
// draw bars behind lines
drawOrder = arrayOf(
CombinedChart.DrawOrder.BAR,
CombinedChart.DrawOrder.BUBBLE,
CombinedChart.DrawOrder.CANDLE,
CombinedChart.DrawOrder.LINE,
CombinedChart.DrawOrder.SCATTER
)
legend.apply {
isWordWrapEnabled = true
verticalAlignment = Legend.LegendVerticalAlignment.BOTTOM
horizontalAlignment = Legend.LegendHorizontalAlignment.CENTER
orientation = Legend.LegendOrientation.HORIZONTAL
setDrawInside(false)
}
axisRight.apply {
setDrawGridLines(false)
setDrawAxisLine(false)
setDrawLabels(false)
axisMinimum = 0f
}
axisLeft.apply {
setDrawGridLines(false)
setDrawAxisLine(false)
setDrawLabels(false)
axisMinimum = 0f
}
val formatter = object : ValueFormatter() {
override fun getAxisLabel(value: Float, axis: AxisBase?): String {
val index = value.toInt() % months.size
return months[index]
}
}
xAxis.apply {
setDrawGridLines(false)
setDrawAxisLine(false)
position = XAxis.XAxisPosition.BOTTOM
axisMinimum = 0f - 0.25f
granularity = 1f
valueFormatter = formatter
axisMaximum = combinedData.xMax + 0.25f
}
}
}

private fun generateLineData(dataList: MutableList<Entry>): LineData {
val lineData = LineData()
val lineDataSet = LineDataSet(dataList, "Line DataSet")
lineDataSet.apply {
color = resources.getColor(R.color.purple_200, resources.newTheme())
valueTextSize = 12f
valueTextColor = Color.BLACK
setDrawValues(true)
mode = LineDataSet.Mode.CUBIC_BEZIER
lineWidth = 2f
axisDependency = YAxis.AxisDependency.LEFT
}
lineData.addDataSet(lineDataSet)
return lineData
}

private fun generateBarData(dataList: MutableList<BarEntry>): BarData {
val dataSet = BarDataSet(dataList, "Bar")
dataSet.apply {
color = ResourcesCompat.getColor(resources, R.color.light_green, resources.newTheme())
valueTextColor = Color.TRANSPARENT
dataSet.valueTextSize = 10f
axisDependency = YAxis.AxisDependency.LEFT
}
val barWidth = 0.45f // x2 dataset
// (0.45 + 0.02) * 2 + 0.06 = 1.00 -> interval per "group"
val barData = BarData(dataSet)
barData.barWidth = barWidth
return barData
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package com.example.cyclofit.ui.fragment

import android.content.Context
import android.graphics.Color
import android.graphics.Typeface
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.example.cyclofit.databinding.FragmentDistancePieChartBinding
import com.example.cyclofit.model.Shared
import com.example.cyclofit.ui.utils.ChartUtils
import com.example.cyclofit.ui.utils.Constants
import com.github.mikephil.charting.animation.Easing
import com.github.mikephil.charting.components.Legend
import com.github.mikephil.charting.data.Entry
import com.github.mikephil.charting.data.PieData
import com.github.mikephil.charting.data.PieDataSet
import com.github.mikephil.charting.data.PieEntry
import com.github.mikephil.charting.formatter.ValueFormatter
import com.github.mikephil.charting.highlight.Highlight
import com.github.mikephil.charting.listener.OnChartValueSelectedListener
import com.github.mikephil.charting.utils.MPPointF
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import java.lang.reflect.Type

class DistancePieChartFragment : Fragment(), OnChartValueSelectedListener {
lateinit var binding: FragmentDistancePieChartBinding
var sp = ArrayList<Shared>()

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentDistancePieChartBinding.inflate(inflater, container, false)
val sharedPreferences1 = requireContext().getSharedPreferences(
Constants.CYCLOFIT_PREFERENCES,
Context.MODE_PRIVATE
)
val gson = Gson()
val json = sharedPreferences1.getString("timer", null)
val type: Type = object : TypeToken<ArrayList<Shared?>?>() {}.type

if (json != null) {
sp = gson.fromJson(json, type)
}

val distanceArrayList = ArrayList<String>()
for (i in 0 until sp.size) {
distanceArrayList.add("0")
}
val pieDataList = mutableListOf<PieEntry>()
for ((x, i) in distanceArrayList.withIndex()) {
pieDataList.add(PieEntry(i.toFloat(), "${i.toFloat()} km"))
}
setPieChart(pieDataList)
return binding.root
}

private fun setPieChart(dataList: MutableList<PieEntry>) {
val dataSet = PieDataSet(dataList, "My Graph")
dataSet.apply {
valueTextSize = 16f
valueTextColor = Color.BLACK
setDrawIcons(false)
sliceSpace = 3f
iconsOffset = MPPointF(0f, 40f)
selectionShift = 5f
colors = ChartUtils.colors
}

binding.pieChart.apply {
isDrawHoleEnabled = false
description.isEnabled = false
setUsePercentValues(true)

// entry label text styling
setEntryLabelColor(Color.TRANSPARENT) // hide label text in Pie chart
setEntryLabelTypeface(Typeface.DEFAULT)
setEntryLabelTextSize(16f)

dragDecelerationFrictionCoef = 0.95f
rotationAngle = 0f
// enable rotation of the chart by touch
isRotationEnabled = true
isHighlightPerTapEnabled = true

animateY(1400, Easing.EaseInOutQuad)

legend.apply {
verticalAlignment = Legend.LegendVerticalAlignment.TOP
horizontalAlignment = Legend.LegendHorizontalAlignment.RIGHT
orientation = Legend.LegendOrientation.VERTICAL
setDrawInside(false)
xEntrySpace = 7f
yEntrySpace = 0f
yOffset = 0f
}
val pieData = PieData(dataSet).apply {
setValueFormatter(object : ValueFormatter() {
override fun getPieLabel(value: Float, pieEntry: PieEntry?): String {
return "${String.format("%.1f", value)} %"
}
})
}
data = PieData(dataSet)
highlightValues(null) // undo all highlights
invalidate()
}
// add a selection listener
binding.pieChart.setOnChartValueSelectedListener(this)
}

override fun onValueSelected(e: Entry?, h: Highlight?) {
if (e == null)
return
}

override fun onNothingSelected() {
return
}
}
Loading

0 comments on commit 9ebb85d

Please sign in to comment.