Cheondi
개발 · 알고리즘·CS

차트에 그린 선은 어디까지 저장해야 할까

차트 드로잉 도구를 저장하며 화면 좌표와 데이터 좌표의 차이를 배운 기록입니다.

  • #chart
  • #coordinate
  • #serialization

차트에 추세선을 그리고 다음 실행에도 남기는 기능을 살펴봤다. 처음에는 선의 시작점과 끝점 좌표를 JSON에 넣으면 된다고 생각했다. 실제로 화면의 x, y 픽셀을 저장해 보니 창 크기나 차트 구간이 바뀌는 순간 선이 엉뚱한 위치로 이동했다.

화면 좌표는 지금 보이는 결과였고, 사용자가 의도한 건 특정 시각과 가격을 잇는 선이었다. 저장해야 하는 건 픽셀이 아니라 차트 데이터의 의미였다.

화면 좌표를 데이터 좌표로 바꿨다

드래그 중에는 포인터 위치를 사용하지만 저장 직전에는 x축의 시각과 y축의 가격으로 변환했다.

public record DrawingPoint(long Timestamp, decimal Price);

DrawingPoint ToDataPoint(Vector2 screen)
{
    return new DrawingPoint(
        chart.TimeAt(screen.x),
        chart.PriceAt(screen.y));
}

다시 그릴 때는 현재 화면 범위와 크기에 맞춰 데이터 좌표를 화면 좌표로 바꿨다. 확대·축소해도 같은 시각과 가격을 가리켰다.

도구마다 필요한 값이 달랐다

추세선은 점 두 개면 됐지만 수평선은 가격 하나, 사각형은 두 점과 스타일이 필요했다. 모든 값을 nullable 필드로 넣은 거대한 객체보다 공통 정보와 도구별 데이터를 구분했다.

{
  "type": "trend-line",
  "version": 1,
  "points": [
    { "time": 1700000000, "price": "102.25" },
    { "time": 1700003600, "price": "103.10" }
  ],
  "style": { "color": "#4B7BEC", "width": 2 }
}

가격은 부동소수점 오차를 줄이기 위해 문자열이나 정해진 decimal 규칙으로 다뤘다.

저장 시점과 삭제도 정했다

포인터가 움직일 때마다 파일을 쓰면 불필요한 I/O가 많았다. 그리기가 끝났을 때 메모리 상태를 갱신하고 짧게 모아서 저장했다. 삭제한 도구가 다음 실행에서 다시 나타나지 않도록 삭제와 저장도 하나의 흐름으로 묶었다.

파일 쓰기 도중 종료될 가능성을 줄이기 위해 임시 파일에 쓴 뒤 교체하는 방식도 확인했다. 깨진 파일을 만났을 때는 전체 앱이 멈추지 않고 해당 도구 데이터만 복구 대상으로 표시했다.

저장은 의도를 보존하는 일이었다

좌표 숫자 두 개를 남기는 일이라 생각했지만 어떤 환경이 바뀌어도 같은 의미를 유지해야 했다. 화면 크기와 확대 배율은 표현이었고 시각과 가격은 사용자 의도에 가까웠다.

이후 상태를 저장할 때 현재 UI 값을 그대로 복사하기 전에 그 값이 원본인지 계산된 결과인지 생각한다. 차트 선 하나가 데이터 모델과 표현 모델을 구분하게 해 준 작업이었다.