[안드로이드 - 코틀린] MPAndroidChart 라이브러리를 이용해 Linechart 그래프 그리기 (정적)

2022. 12. 15. 15:11Android

시작 전 나와 같은 초보자들이 참고하면 좋았을 법한 사항들 

  • 작업 끝나고 후회는 점은 기간이 급박하여 시작 전 어떤 식으로 작업할지 계획을 잡지 않고 주먹구식으로 그때그때 수정하는 작업을 했다 
  • 특히 기존 블로그들에 올라와있는 작업들 대부분 데이터가 앱 내부에 존재해 로딩? 혹은 데이터를 받을 시간을 주지 않아도 되었기 때문에 그 방식 그대로 했다가 잦은 오류가 발생되었다 -> 특히 실시간 처리가 필요한 부분에 적절한 handler을 줘야 ui가 업데이트가 될 수 있다 특히 차트가 6개나 있어서 꼭 필요한 작업이었다 
  • 시간적인 여유가 있으면 handler과 어떤식으로 할지에 대한 기본적인 틀을 작성하고 시작했으면 좋겠다
  • 그리고 제일 중요한 생명주기 공부가 필수이다 처음 시작할 때는 만만히 보고 시작했지만 생각보다 너무너무 중요한 개념이다 

코드 작성 환경은 Activity가 아닌 TabLayout 내에 있는 Fragment입니다

1.  MPAndroidChart 라이브러리 불러오기 

일단 라이브러리를 사용하기 위한 gradle 세팅 현재 사용한 버전입니다 

implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'

2. 그래프 중 차트 그래프 이용 

-> 일단 실시간 데이터가 차트에 추가되는 형식이 아닌 데이터 묶음을 불러와 다른 페이지에 넘어가서 돌아올 때마다 새로 로딩해 업데이트하는 방식으로 진행했다

 

상단 프래그먼트에서 차트 선언은 var 형식으로 진행했다 기본차트를 사용하는 게 아닌 차트 커스텀을 진행해줘야 되기 때문에 꼭 필요하다 

lateinit var chart: LineChart
lateinit var chart2: LineChart
lateinit var chart3: LineChart
lateinit var chart4: LineChart
lateinit var chart5: LineChart
lateinit var chart6: LineChart

 

onCreateView 내부에 차트별로 아이디 값을 할당시켜줬다 

chart = view.findViewById(R.id.LineChart)
chart2 = view.findViewById(R.id.LineChart2)
chart3 = view.findViewById(R.id.LineChart3)
chart4 = view.findViewById(R.id.LineChart4)
chart5 = view.findViewById(R.id.LineChart5)
chart6 = view.findViewById(R.id.LineChart6)

 

위에서 말했듯 지속적으로 화면 전환후 다시 갱신을 해줘야 되기 때문에 코드 메인 코드는 onResume에서 진행

 

  1. line_data는 데이터 값들을 넣어줄 변수 이름
  2. dataSets들은 데이터 넣어주는 틀이다 
  3. dataList들은 내가 소켓을 통해 받아온 데이터를 넣어줄 공간이다 
  4. 차트에 지연에 사용되는 handler 
  5. 차트 내 데이터를 클릭하면 나오는 이벤트 뷰 
var line_data1 : LineData
var line_data2 : LineData
var line_data3 : LineData
var line_data4 : LineData
var line_data5 : LineData
var line_data6 : LineData


// 데이터 넣는 데이터 셋
val dataSets1 = ArrayList<ILineDataSet>()
val dataSets2 = ArrayList<ILineDataSet>()
val dataSets3 = ArrayList<ILineDataSet>()
val dataSets4 = ArrayList<ILineDataSet>()
val dataSets5 = ArrayList<ILineDataSet>()
val dataSets6 = ArrayList<ILineDataSet>()

// 어디선가 가져온 데이터를 리스트에 넣는데
val dataList1 = ArrayList<Entry>()
val dataList2 = ArrayList<Entry>()
val dataList3 = ArrayList<Entry>()
val dataList4 = ArrayList<Entry>()
val dataList5 = ArrayList<Entry>()
val dataList6 = ArrayList<Entry>()

// 차트에서 데이터 처리하는 시간과 그려주는 시간을 주기위해 지연시간을 줌
val mHandler = Handler(Looper.getMainLooper())

// 차트 데이터 클릭시 나타날 아이콘
val marker = MyMarkerView(mainActivity, R.layout.custom_marker_view)

inner class로 클릭시 나타나는 데이터를 따로 설정해줘야 원하는 것이 차트에 나온다 -> 내가 필요한 건 날짜여서 날짜 데이터를 x 값 대신 넣어줬다 

@SuppressLint("ViewConstructor")
inner class MyMarkerView(context: Context?, layoutResource: Int) : MarkerView(context, layoutResource) {
    private val tvContent: TextView = findViewById(R.id.tvContent)

    // runs every time the MarkerView is redrawn, can be used to update the
    // content (user-interface)
    override fun refreshContent(e: Entry, highlight: Highlight) {

        val mFormat = DecimalFormat("0")

        if (e is CandleEntry) {
            tvContent.text = "17"

        } else {

            for (i in (mFormat.format(e.x).toInt()) until socket_data.size){

                val j = socket_data.size - 1
                tvContent.text = "${(socket_data[j - mFormat.format(e.x).toInt()].date)}"
            }

        }
        super.refreshContent(e, highlight)
    }

    override fun getOffset(): MPPointF {
        return MPPointF((-(width / 2)).toFloat(), (-height).toFloat())
    }

}

 

코드 전체 부분은 아니고 데이터리스트에 소켓에서 받아온 데이터를 넣어준다 entry 형식은 가져오는 데이터 형식에 맞춰주면 된다

dataList1.add(Entry(i.toFloat(), socket_data[j].Tc))

차트 뷰를 이용해 차트 클릭시 나오는 이벤트 ( 커스텀 없이 작성하면 차트 내에 x 좌표 위치 정보가 나옵니다 저는 소켓을 통해 받아온 데이터에 시간 데이터가 있어 그걸 표현해주고 싶어서 따로 만들었습니다 

marker.chartView = chart
chart.marker = marker

 

차트 데이터와 차트 이름을 정함

val lineDataSet1 = LineDataSet(dataList1, "차트 이름 넣는 곳")

 

createSet은 내가 차트 내부 데이터의 환경을 설정하는 함수이다 따로 Resume 밖에 만들었다

//1. 데이터 셋 만들기
createSet(lineDataSet1)
private fun createSet(set: LineDataSet): LineDataSet {


    set.apply {
        axisDependency = YAxis.AxisDependency.LEFT
        color = getColor(R.color.red)
        setCircleColor(color)
        valueTextSize = 15f
        lineWidth = 1f
        circleRadius = 3f
        fillAlpha = 0
        fillColor = getColor(R.color.white)
        setDrawValues(true)

    }
    return set
}

 

dataSet에 위에서 만들어준 lineDataSet을 추가해준다

//2. 리스트에 데이터셋 추가
dataSets1.add(lineDataSet1)

 

line_data에 위에서 만들어준 dataSet을 넣어준다 

//3. 라인 데이터에 리스트 추가
line_data1 = LineData(dataSets1)

 

chart_shape는 차트 내부에 x,y좌표별 조건들을 만들고 차트 내부에 그려지는 형식을 정해준다

chart_shape(chart)

매우매우 중요 차트를 그릴 때 setVisblexRangeMaximum이 말을 안들을 경우 차트 초기화 이후에 적용하면 작동하는 것을 알 수 있다 이걸 몰라서 매우매우 고생함

private fun chart_shape(set_chart: LineChart) {

    mainActivity.runOnUiThread {
        set_chart.animateX(2000)
        set_chart.setTouchEnabled(true)
        set_chart.setVisibleXRangeMaximum(5f)
        set_chart.invalidate()
        set_chart.setPinchZoom(false)
        set_chart.isDoubleTapToZoomEnabled = false
        set_chart.setExtraOffsets(8f, 16f, 8f, 16f)
        set_chart.description = null
        set_chart.isScaleXEnabled = false
        set_chart.isScaleYEnabled = false
    }

    set_chart.xAxis.apply {
        position = XAxis.XAxisPosition.BOTTOM
        setDrawGridLines(false)
        axisMaximum = 18.5f
        axisMinimum = -0.5f

    }

    set_chart.axisRight.apply {
        setDrawGridLines(false)
        setDrawLabels(false)
        setDrawAxisLine(false)
    }

}

 

Custom_Legend는 차트의 폰트 사이즈와 간격을 설정해 주었다

Custom_Legend(chart)
private fun Custom_Legend(set_chart: LineChart) {

    val legend: Legend = set_chart.legend

    legend.formSize = 10f
    legend.textSize = 15f
}