반응형

안녕하세요
오늘 적어볼 건

안드로이드에서 다뤄야할때 무조건 접할 수밖에 없는 MPAndroidChart인데요

꺾은선 그래프, 막대 그래프 등

https://github.com/PhilJay/MPAndroidChart

line chart, bar chart 

 

앱에 넣어야한다? 그럼 무조건 MPAndroidChart 를 사용할 수 밖에 없는 상황이거든요.

이걸 일일이 만든다고 상상하면 노가다 ㅋㅋ 티스푼으로 국밥먹는 느낌... 
아무튼?

사실 바차트나 라인차트나 다 비슷해요 

원리를 피그잼으로 직접 그려본다면 

차트에 들어갈 값들을 List<Entry>로 바꾸고 
-> 이 엔트리는 RaderChart, LineChart, BarChart 등 다 다름. 맞게 넣으면 됨.

그리고 차트 속성, XAxis, YAxis 등 여러 속성들을 입맛대로 수정하면 됨. 
근데 속성들이 처음에는 많고 생소해서 차트 라이브러리에 진입장벽이 미세먼지만큼 존재함. 

차트중에는 차트자체에 속성도 있는 부분도 존재하긴 하더라구요 아무튼

 

뭐 이것저것 차트에 대해서 적는건 무의미하고, 제가 적을 건 BarChart의 Bar를 Radius를 줄 수 없는가? 입니다.

 

정답은 Yes고 이 내용에 대해서 github issue 같은 곳을 뒤져봤을 때, 좀 시간이 지난 글이라 버전이 좀 안맞을 수도 있을 것 같아. 이것저것 수정해보면서 완료해서 공유해놓으려구요.

 

MPAndroidChart BarChart Bar Round Shape


 

먼저 위에서 말했던 기본 차트의 로직도 필요합니다. 왜냐면, radius를 처리한 bar들을 렌더링 해주는 클래스를 barChart를 선언하자마자 불러와야 하거든요. 

 

val barChart: BarChart = binding.barChart

// ------# 이곳에서 radius를 만드는 렌더링 클래스와 연결 #------
barChart.renderer = BarChartRender(barChart, barChart.animator, barChart.viewPortHandler)
val entries = ArrayList<BarEntry>()

for (i in DataSets.indices) {
   val entry = BarEntry(i.toFloat(), DataSets[i])
   entries.add(entry)
}
val dataSet = BarDataSet(entries, "")
dataSet.apply {
    color =  resources.getColor(Color.Red, null)
    setDrawValues(false)
}

val bcdata = BarData(dataSet)
bcdata.apply {
    barWidth = 0.5f
}
barChart.data = bcdata

// X축 설정
barChart.xAxis.apply {
    position = XAxis.XAxisPosition.BOTTOM
    setDrawGridLines(false)
    setDrawAxisLine(false)
    labelRotationAngle = 2f
    setDrawLabels(false)
}
barChart.legend.apply {
    formSize = 0f
}
// 왼쪽 Y축 설정
barChart.axisLeft.apply {
    axisMinimum = -1f // Y축 최소값
    setDrawAxisLine(false)
    setDrawGridLines(false)
    setLabelCount(0, false)
    setDrawLabels(false)
}
// 차트 스타일링 및 설정
barChart.apply {
    axisRight.isEnabled = false
    description.isEnabled = false
    legend.isEnabled = false
    setDrawValueAboveBar(false)
    setDrawGridBackground(false)
    setFitBars(false)
    animateY(500)
    setScaleEnabled(false)
    setTouchEnabled(false)
    invalidate()
}


렌더링 부분이후에는 

순서대로 

val entries = ArrayList<BarEntry>()
for (i in DataSets.indices) {    
    val entry = BarEntry(i.toFloat(), DataSets[i])
    entries.add(entry)
}
entry로 변환해주구요 
val dataSet = BarDataSet(entries, "")
dataSet.apply {
    color =  resources.getColor(Color.Red, null)
    setDrawValues(false)
}

그리고 데이터셋으로 만듭니다, 아마 BarDataSet(entries, "")의 두번째 인자는 legend였나..? 기억이 잘.. 걍 넣어보면 됨

아무튼 그 하단은 각각의 변수명대로 속성을 정의하는 부분입니다.  ㅅㄱ

그리고 이제 이 글의 목적인 BarChartRender입니다. 

BarDataProvider라는 클래스가 있더라구요..? 그리고 viewPortHandler는 뷰포트라는.. 비전공자로써 하나 알아갑니다 

 

아무튼 chart에는 BarChart에 들어갈 데이터가 들어갑니다. 그리고 동일하게 canvas로 그리는 부분은 뭐 다른 부분과 비슷하니 생략하구. 

class BarChartRender(chart: BarDataProvider?, animator: ChartAnimator?, viewPortHandler: ViewPortHandler?) : BarChartRenderer(chart, animator, viewPortHandler) {
    private var mRightRadius = 16f
    private var mLeftRadius = 16f
    fun setRightRadius(mRightRadius: Float) {
        this.mRightRadius = mRightRadius
    }

    fun setLeftRadius(mLeftRadius: Float) {
        this.mLeftRadius = mLeftRadius
    }

    override fun drawDataSet(c: Canvas, dataSet: IBarDataSet, index: Int) {
        val trans = mChart.getTransformer(dataSet.axisDependency)
        mShadowPaint.color = dataSet.barShadowColor
        val phaseX = mAnimator.phaseX
        val phaseY = mAnimator.phaseY

        val buffer = mBarBuffers[index]
        buffer.setPhases(phaseX, phaseY)
        buffer.setDataSet(index)
        buffer.setBarWidth(mChart.barData.barWidth)
        buffer.setInverted(mChart.isInverted(dataSet.axisDependency))
        buffer.feed(dataSet)
        trans.pointValuesToPixel(buffer.buffer)
        
        if (dataSet.colors.size > 1) {
            var j = 0
            while (j < buffer.size()) {
                if (!mViewPortHandler.isInBoundsLeft(buffer.buffer[j + 2])) {
                    j += 4
                    continue
                }
                if (!mViewPortHandler.isInBoundsRight(buffer.buffer[j])) break
                if (mChart.isDrawBarShadowEnabled) {
                    if (mRightRadius > 0) {
                        c.drawRoundRect(
                            RectF(buffer.buffer[j], mViewPortHandler.contentTop(),
                            buffer.buffer[j + 2],
                            mViewPortHandler.contentBottom()), mRightRadius, mRightRadius, mShadowPaint)
                    } else {
                        c.drawRect(buffer.buffer[j], mViewPortHandler.contentTop(),
                            buffer.buffer[j + 2],
                            mViewPortHandler.contentBottom(), mShadowPaint)
                    }
                }

                mRenderPaint.color = dataSet.getColor(j / 4)
                if (mRightRadius > 0) {
                    c.drawRoundRect(RectF(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2],
                        buffer.buffer[j + 3]), mRightRadius, mRightRadius, mRenderPaint)
                } else {
                    c.drawRect(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2],
                        buffer.buffer[j + 3], mRenderPaint)
                }
                j += 4
            }
            
        } else {
            mRenderPaint.color = dataSet.color
            var j = 0
            while (j < buffer.size()) {
                if (!mViewPortHandler.isInBoundsLeft(buffer.buffer[j + 2])) {
                    j += 4
                    continue
                }
                if (!mViewPortHandler.isInBoundsRight(buffer.buffer[j])) break
                if (mChart.isDrawBarShadowEnabled) {
                    if (mRightRadius > 0) c.drawRoundRect(RectF(buffer.buffer[j], mViewPortHandler.contentTop(),
                        buffer.buffer[j + 2],
                        mViewPortHandler.contentBottom()), mRightRadius, mRightRadius, mShadowPaint) else c.drawRect(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2],
                        buffer.buffer[j + 3], mRenderPaint)
                }
                if (mRightRadius > 0) {
                    c.drawRoundRect(RectF(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2],
                        buffer.buffer[j + 3]), mRightRadius, mRightRadius, mRenderPaint)
                } else {
                    c.drawRect(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2],
                        buffer.buffer[j + 3], mRenderPaint)
                }
                j += 4
            }
        }
    }
}

 

이 클래스는 MPAndroidChart에서 제공하는 BarChartRenderer(chart, animator, viewPortHandler) 를 반환합니다.
여기서 내장된 override fun drawDataSet 함수는 chart라는 BarChartProvider의 값들을 가져오구요.

중간에 radius는 16F 로 설정한 곳 이후에 

mBarBuffers[index]는 
매개변수로 받은 chart라는 값에 들어간 각각의 bar data들을 따로 담는 곳입니다.  데이터들이 그려지기 전에요.

그리고 각각의 Bar들의 색을 결정하고, 그 그림자를 만드는 로직이구요.. 
사실 Rect는 어려워서 흥미가 떨어지는점.

 

현재 회사에서는 Bitmap을 가져와서 draw까지는 하겠는데 저 Rect는 참 어려운 것 같아요

 

아무튼 BarChartRender를 사용하는 화면단위에서 가져와서 사용하면? 짜잔. 

노가다로 수직 프로그레스바를 여러개 겹쳐도 상관없을 것 같기도 하고 흠.. 아무튼 완성

반응형