이번엔 다음과 같이 Bar Chart를 그려볼 예정입니다. 역시 간단한 Bar Chart입니다.
먼저 다음과 같이 BarChartView라는 Custom View를 생성하고, onDraw함수를 override해 줍니다.
이 onDraw() 함수내에서 원하는 그림을 그리면 화면에 나타나게 됩니다.
class BarChartView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
}
}
draw함수내에 다음과 같이 x, y 축을 그리는 코드를 작성해보았습니다.
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas ?: return
val leftSpace = 90f
val topSpace = 60f
val rightSpace = 60f
val bottomSpace = 90f
// x, y 좌표축 라인 그리기
paint.color = Color.BLACK
val axisLineWidth = 1f
val halfAxisLineWidth = axisLineWidth / 2f
paint.strokeWidth = axisLineWidth
val xAxisY = height - bottomSpace - halfAxisLineWidth
canvas.drawLine(leftSpace, xAxisY, width - rightSpace, xAxisY, paint)
val yAxisX = leftSpace + halfAxisLineWidth
canvas.drawLine(yAxisX, topSpace, yAxisX, height - bottomSpace, paint)
}
다음과 같이 x, y축이 그려지는 것을 확인할 수 있습니다.
위 코드를 보시면 xAxisY, yAxisX를 계산할때 halfAxisLineWidth값을 더하고 빼는 것을 볼수 있는데요, 이는 아래 좌측 그림과 같이 drawLine()함수를 이용하여 line을 그릴때 그 기준이 되는 선이 노란색 점선이기 때문입니다.
(노란색 점선을 중심으로 하여 paint.strokeWidth가 적용이 됩니다.)
만일, y좌표축 라인을 다음과 같이 그냥 leftSpace을 x값으로 놓고 그리게 되면, 아래 우측 그림과 같이 의도한 것과는 다르게 좌측으로 치우쳐져 그려지게 됩니다.
val yAxisX = leftSpace
canvas.drawLine(yAxisX, topSpace, yAxisX, height - bottomSpace, paint)
![]() |
![]() |
이제, 앞선 LineChartView와 마찬가지로 Int형 배열을 데이터로 전달받아 각 값을 Bar형태로 그려보겠습니다.
Bar Chart를 그리는 기본 개념은 다음과 같습니다.
- 테두리 space및 x,y좌표축 라인을 제외한 나머지 노란색 부분이 실제 Bar Chart가 그려질 영역입니다.
- 노란색 영역의 width를 data 개수로 나누면 1개 item이 차지해야 할 공간을 계산할수 있습니다. itemWidth로 표시한 부분입니다.
- 각각의 item 공간 가운데를 기준으로, lineWidth를 barWidth로 설정하여 라인을 그려줍니다.
(이때 당연히 barWidth는 itemWidth보다 작아야 하므로 itemWidth * 0.7 과 같이 비율로 계산해주면 좋을것 같습니다.)
이를 코드로 작성해보면 다음과 같습니다.
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
......
val dataList = dataList ?: return
if (dataList.isEmpty()) {
return
}
// View는 원점이 좌상단임을 고려하여 상/하 반전 처리
val mappedDataList = dataList.map { value ->
height - bottomSpace - topSpace - axisLineWidth - value.toFloat()
}
paint.color = Color.MAGENTA
var x = leftSpace + axisLineWidth // 노란색 영역의 x축 시작점을 계산
val itemWidth = (width - x - rightSpace) / mappedDataList.count() // 1개 bar item이 그려질 공간을 계산
val barWidth = itemWidth * 0.7f // item 영역내 그려질 bar의 두께를 itemWidth보다 작게 계산
x += itemWidth / 2 // 첫번째 bar는 item영역내 x축을 기준으로 가운데에 그려야 하므로
paint.strokeWidth = barWidth
mappedDataList.forEach { value ->
canvas.drawLine(x, height - bottomSpace - axisLineWidth, x, value, paint)
x += itemWidth
}
}
fun setDataList(dataList: ArrayList<Int>) {
if (dataList.isEmpty()) {
return
}
this.dataList = dataList
invalidate()
}
데이터는 다음과 같이 설정하여 차트를 그렸습니다.
chartView.setDataList(arrayListOf(0, 30, 45, 150, 600, 270, 300, 210, 90))
x, y좌표축 라인 두께가 얇아서 티가 잘 나지 않는데요, 라인 두께를 좀 두껍게 해서 다시 그려보면 다음과 같습니다.
좌표축 라인과 bar가 서로 겹치지 않고 그려지는 것을 확인할수 있습니다.
헌데, 아직 문제가 남아있습니다. 앞서 Line Chart도 마찬가지였는데요, 전달되는 data값이 Bar Chart영역의 높이를 벗어나는 경우에 대한 대비가 이루어지지 않았습니다.
이를 위해 data목록 중 가장 큰값이 위에 "Bar Chart를 그리는 기본 개념"에서 보여드린 그림의 노란색 영역의 높이가 될수 있도록 보정하는 코드를 추가한 전체 코드는 다음과 같습니다.
class BarChartView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
private var dataList: ArrayList<Int>? = null
private val paint = Paint().apply {
isAntiAlias = true
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas ?: return
val leftSpace = 90f
val topSpace = 60f
val rightSpace = 60f
val bottomSpace = 90f
// x, y 좌표축 라인 그리기
paint.color = Color.BLACK
val axisLineWidth = 1f
val halfAxisLineWidth = axisLineWidth / 2f
paint.strokeWidth = axisLineWidth
val xAxisY = height - bottomSpace - halfAxisLineWidth
canvas.drawLine(leftSpace, xAxisY, width - rightSpace, xAxisY, paint)
val yAxisX = leftSpace + halfAxisLineWidth
canvas.drawLine(yAxisX, topSpace, yAxisX, height - bottomSpace, paint)
val dataList = dataList ?: return
if (dataList.isEmpty()) {
return
}
val maxValue = dataList.maxOrNull() ?: return
var scale = 1f
// 값이 뷰 영역을 벗어나지 않고 화면내에 그려질수 있도록 비율 계산
val chartAreaHeight = height - bottomSpace - topSpace - axisLineWidth
if (maxValue > chartAreaHeight) {
scale = chartAreaHeight / maxValue
}
// View는 원점이 좌상단임을 고려하여 상/하 반전 처리
val mappedDataList = dataList.map { value ->
height - bottomSpace - topSpace - axisLineWidth - value.toFloat() * scale
}
paint.color = Color.MAGENTA
var x = leftSpace + axisLineWidth // 노란색 영역의 x축 시작점을 계산
val itemWidth = (width - x - rightSpace) / mappedDataList.count() // 1개 bar item이 그려질 공간을 계산
val barWidth = itemWidth * 0.7f // item 영역내 그려질 bar의 두께를 itemWidth보다 작게 계산
x += itemWidth / 2 // 첫번째 bar는 item영역내 x축을 기준으로 가운데에 그려야 하므로
paint.strokeWidth = barWidth
mappedDataList.forEach { value ->
canvas.drawLine(x, height - bottomSpace - axisLineWidth, x, value + topSpace, paint)
x += itemWidth
}
}
fun setDataList(dataList: ArrayList<Int>) {
if (dataList.isEmpty()) {
return
}
this.dataList = dataList
invalidate()
}
}
데이터를 다음과 같이 화면을 벗어나는 값을 설정하여, 값 보정전과 후가 어떻게 다르게 표시되는지 확인해 보았습니다.
chartView.setDataList(arrayListOf(0, 30, 45, 150, 1500, 270, 300, 210, 90))
![]() |
![]() |
좌측의 경우 5번째 항목이 Chart영역 밖을 벗어났지만, 우측의 경우 Chart영역 내에서만 그려지는 것을 볼수 있습니다.
이렇게 간단한 Bar Chart를 그리는 방법을 알아보았습니다.
'Android > 차트그리기' 카테고리의 다른 글
5. Android Radar Chart에 Animation 적용하기 (0) | 2022.06.30 |
---|---|
4. Android Radar Chart 직접 그리기 (0) | 2022.06.28 |
3. Android Pie Chart 직접 그리기 (0) | 2022.06.27 |
1. Android Line Chart 직접 그리기 (0) | 2022.06.17 |