그래프 라이브러리 3. 그래프 데이터 그리기
Last updated: Dec 13, 2023
이번에 부스트캠프 그룹 프로젝트를 하면서 그래프 라이브러리를 구현한 과정을 정리해보았다. 이번 포스팅에서는 그 중에서 그래프 데이터를 그리는 부분에 대해 설명하고자 한다. (project repo, library repo)
그래프 디자인 모티프
우선 구현방법을 설명하기 전에 왜 이 라이브러리를 구현하게 됐는지 설명해야한다.
부스트캠프 그룹 프로젝트 디자인을 하면서 Material Design을 준수하는 디자인으로 제작하고자 하였고, 그래프를 디자인도 Material스럽게 반영을 하고자 했다.

초기 디자인

그래프 모티프
그래프 디자인의 영감은 자주 사용하고 있는 Google Finance Widget에서 가져왔다. 해당 위젯의 주식 그래프가 정말 Material스러운 디자인이라고 생각해서, 초기 디자인에 반영하였다.
Android 그래프 라이브러리 중에 가장 유명한건 MPAndroidChart이다. 처음 조사를 하게 되면서 이 라이브러리는 사용하지 않기로 결정하였다. 그 이유는 19년 이후로 릴리즈가 없는 유지보수가 되고 있지 않은 라이브러리이고, 또한 프로젝트를 디자인한대로 디자인을 Material스럽게 커스터마이징 하기 어렵다는 생각이 들었다. 우리 프로젝트에 맞게끔 디자인도 맞추고 기능도 맞추고, 그래프 라이브러리를 직접 구현하는것이 기술적인 도전이 될 것이라 판단하여 라이브러리를 직접 구현하게 되었다.
구현 방법
그라데이션 입히기
Google Finance Widget의 그라데이션을 잘 살펴보면, 그래프 선의 모양과는 상관 없이 높은곳은 그라데이션의 정도가 동일하고, 낮은 곳은 그라데이션이 동일하게 마무리 되어 있다. 즉, y축 방향에 평행하게끔 그라데이션이 진행된다. 따라서 그라데이션을 먼저 그래프 배경으로 그리고, 그래프에서 필요없는 윗 부분의 그라데이션은 배경색으로 덧칠을 하고 그래프를 그리는 방식으로 구현하고자 한다.
val chartSpaceEndY =
Px(height.toFloat()) - yAxisMarginStart.toPx(context) - Px(axisStrokeWidth)
// Set gradation position, color, mode
val gradationEndY = Px(height.toFloat()) - yAxisMarginStart.toPx(context)
gradientPaint.shader =
LinearGradient(
graphSpaceStartX.value,
graphSpaceStartY.value,
graphSpaceStartX.value,
gradationEndY.value,
ColorUtils.setAlphaComponent(colorPrimary, 180),
Color.TRANSPARENT,
TileMode.CLAMP
)
// Fill gradation
canvas.drawRect(
graphSpaceStartX.value,
graphSpaceStartY.value + 1f,
graphSpaceEndX.value,
chartSpaceEndY.value,
gradientPaint
)
선 바깥 그라데이션 지우기
그라데이션을 지우는 부분은 배경색으로 덧칠을 하는 부분이다. 그래프의 각 데이터를 이용해 위치값을 계산하는 코드가 있는데, 이는 선 그리기에서도 동일하게 쓰인다. 각 그래프 선이 그려질 부분의 윗부분을 덧칠하는 방식으로 구현하였다.
gradientCoverPaint.setGradientPaint()
chartData.forEachIndexed { index, data ->
if (index < size - 1) {
val next = chartData[index + 1]
// Calculate position of each data
val startX = Px((data.x - minX) / spaceX) * graphWidth + graphSpaceStartX
val startY = Px(1 - (data.y - minY) / spaceY) * graphHeight + graphSpaceStartY
val endX = Px((next.x - minX) / spaceX) * graphWidth + graphSpaceStartX
// Hide the area that doesn't require a gradation
canvas.drawRect(
startX.value - 1F,
0F,
endX.value + 1F,
startY.value,
gradientCoverPaint
)
}
}
선 그리기
마지막으로 선 그리기이다. 그라데이션을 지우는 부분과 동일하게 위치값을 계산한 후, 해당 부분에 맞게 선을 그리면 된다. 가격이 동일하면 x축과 평행하고, 가격이 오르면 오르기 전 값에서 바로 대각선으로 선을 잇지 않고 오른 시점부터 직각으로 오르게끔 그렸다.
linesPaint.setLinePaint()
chartData.forEachIndexed { index, data ->
if (index < size - 1) {
val next = chartData[index + 1]
// Calculate position of each data
val startX = Px((data.x - minX) / spaceX) * graphWidth + graphSpaceStartX
val startY = Px(1 - (data.y - minY) / spaceY) * graphHeight + graphSpaceStartY
val endX = Px((next.x - minX) / spaceX) * graphWidth + graphSpaceStartX
val endY = Px(1 - (next.y - minY) / spaceY) * graphHeight + graphSpaceStartY
canvas.drawLine(startX.value, startY.value, endX.value, startY.value, linesPaint)
canvas.drawLine(endX.value, startY.value, endX.value, endY.value, linesPaint)
}
}
다음 게시글에서는 그래프에 디자인/테마 색상 적용하는것에 대해 이야기하고자 한다.
이후 일부 포스팅은 다른 사람이 맡아, 그래프 라이브러리 시리즈 4편과 5편의 포스팅은 여기서 확인할 수 있습니다.