안녕하세요
오늘 적어볼 건
안드로이드에서 다뤄야할때 무조건 접할 수밖에 없는 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를 사용하는 화면단위에서 가져와서 사용하면? 짜잔.
노가다로 수직 프로그레스바를 여러개 겹쳐도 상관없을 것 같기도 하고 흠.. 아무튼 완성
'개발 > android_kotlin' 카테고리의 다른 글
[ Kotlin ] 그라데이션 xml 등 (0) | 2024.11.12 |
---|---|
[ Kotlin ] 뱃지 읽음 감지 / 최초 실행 접근 (0) | 2024.11.10 |
[ Kotlin ] elevation이 -1인 토글그룹버튼 만들기 ( UI / UX 개선 ) (0) | 2024.09.15 |
[ Kotlin ] Adapter 뿌셔보기 1 ( 내 머리가 부셔짐 ) (0) | 2024.08.05 |
[ Kotlin ] 탭했을 때 view가 상단화면으로 scroll 되게 하기 (UX 개선) (2) | 2024.06.05 |