카페 주문, 메뉴, 매출을 관리하는 관리자 앱을 만들었는데 매출을 수치로 표현하기보다는 그래프로 표현하는 것이 한 눈에 보기 편할 것 같아 MPAndroidChart 라이브러리를 사용해서 그래프를 구현하였다.
링크 : https://github.com/PhilJay/MPAndroidChart
MPAndroidChart 라이브러리에서는 많은 그래프 모양을 지원한다. 이 중에서 필자는 한달 매출을 LineChart를 사용하여 꺾은선 그래프 형태로 구현하였고, 많이 팔린 음료 Top3는 HorizontalBarChart를 이용하여 수평으로 누운 그래프를 구현하였다.
한달 매출을 위한 LineChart
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<data>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.github.mikephil.charting.charts.LineChart
android:id="@+id/line_chart"
android:layout_width="match_parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:layout_height="300dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
MonthChartFragment
class MonthChartFragment : Fragment() {
private var _binding : FragmentMonthChartBinding? = null
private val binding get() = _binding!!
private lateinit var lineChart : LineChart
private var maxSales = 0
// 부모 Fragment viewModel과 공유
private val salesViewModel : SalesViewModel by viewModels(
ownerProducer = {requireParentFragment()}
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
maxSales = ApplicationClass.sharedPreferenceUtil.getMaxSales()!!
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentMonthChartBinding.inflate(inflater, container, false)
return binding.root
}
@RequiresApi(Build.VERSION_CODES.O)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
lineChart = binding.lineChart
setView()
val markerView = CustomMarkerView(requireContext(), R.layout.custom_marker_view)
lineChart.marker = markerView // MarkerView를 Chart에 설정
lineChart.setOnChartValueSelectedListener(object : OnChartValueSelectedListener {
override fun onValueSelected(e: Entry?, h: Highlight?) {
if (e != null) {
markerView.refreshContent(e, h!!) // 데이터 포인트를 클릭하면 MarkerView에 값을 표시
markerView.visibility = View.VISIBLE // MarkerView 표시
}
}
override fun onNothingSelected() {
markerView.visibility = View.GONE // 아무것도 선택되지 않으면 MarkerView 숨김
}
})
lineChart.setOnClickListener {
markerView.visibility = View.GONE // 차트를 클릭하면 MarkerView 숨김
}
}
@RequiresApi(Build.VERSION_CODES.O)
private fun setView() {
salesViewModel.monthPrice.observe(viewLifecycleOwner){
Log.d(TAG, "그래프 쪽 관찰: $it")
// y축의 최댓값을 결정하기 위해 설정
var maxPrice = 0
val entries: ArrayList<Entry> = ArrayList()
val entryListData = salesViewModel.createMonthGraphData(it)
entries.add(Entry(0f, 0f))
entryListData.forEachIndexed { index, price ->
maxPrice = kotlin.math.max(maxPrice, price)
entries.add(Entry((index + 1).toFloat(), price.toFloat()))
}
// 최댓값 갱신
if (maxPrice > maxSales){
maxSales = maxPrice
ApplicationClass.sharedPreferenceUtil.setMaxSales(maxPrice)
}
val dataSet = LineDataSet(entries, "일별 매출")
dataSet.color = Color.RED
dataSet.valueTextColor = Color.BLACK
dataSet.circleRadius = 4f // 데이터 포인트의 원 크기 설정
dataSet.setCircleColor(ContextCompat.getColor(requireContext(), R.color.coffee_brown)) // 데이터 포인트의 색상 설정
dataSet.setDrawValues(false)
val lineData = LineData(dataSet)
lineChart.apply {
description.isEnabled = false
xAxis.position = XAxis.XAxisPosition.BOTTOM
xAxis.labelCount = entries.size // x축 레이블 수
xAxis.setDrawGridLines(false)
axisLeft.apply {
setDrawGridLines(false)
axisLineColor = Color.BLACK // Y축 라인 색상 설정
textColor = Color.BLACK // Y축 텍스트 색상 설정
setDrawAxisLine(true) // y축 선 그리기
axisLineWidth = 1f // Y축 라인 굵기 설정
axisMaximum = maxSales.toFloat() // 왼쪽 Y축 최댓값 설정
}
axisLeft.setDrawGridLines(false)
axisRight.isEnabled = false
data = lineData
invalidate()
}
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
CustomMarkerView
class CustomMarkerView(context : Context, layoutResource : Int) : MarkerView(context, layoutResource) {
val textView = findViewById<TextView>(R.id.tvContent)
override fun refreshContent(entry : Entry, highlight: Highlight) {
textView.text = "${makeComma(entry.y.toInt())}"
super.refreshContent(entry, highlight)
}
override fun getOffset(): MPPointF {
return MPPointF(-width.toFloat(), -height.toFloat()) // 마커 뷰가 데이터 포인트의 좌하단에 표시되도록 설정
}
private fun makeComma(price : Int) : String{
//천단위 콤마
val comma = DecimalFormat("#,###")
val priceString = comma.format(price) + "원"
return priceString
}
}
LineChart를 구현하기 위해서는
1. Entry 속성의 ArrayList
2. xAxis, aAxis 속성(x축, y축 속성)
3. dataSet 속성
이 필요하다.
entries 리스트에는 그래프에 표시 할 데이터 (x값, y값)를 넣어주면 된다.
필자는 해당 Fragment viewModel에서 그래프를 그릴 데이터를 가공하고 MarkerView 객체를 통해 그래프의 데이터 포인트 클릭 시 값을 불러오는 식으로 구현하였다.
x축, y축 속성은 xAxis, aAxis의 속성을 정의하여 색깔, 선 굵기 등을 정의하였다.
마지막으로 apply 함수에 Invalidate() 함수를 호출하여 그림을 그려주었다.
CustomMarkerView를 구현하기 위해서는
MarkerView 클래스를 상속받아 refreshContent 함수를 override하여 데이터 포인트의 값을 textView로설정하였다.
textView의 위치는 getOffset() 함수로 조정하였다.
Top3 음료도 HorizontalBarChart로 이와 같이 구현하면 된다.