2015년 2월 27일 금요일

VB.Net Chart tip 1 - AxisY Rescaling

  시계열 데이터를 Chart로 표현하려다 보면 이런저런 많은 문제가 생길 수 있습니다.

  대표적인 것이 주가 차트일 텐데요, 증권사에서 제공하는 HTS를 사용하면, 사용자가 보고자하는 종목의 캔들차트와 보조지표를 하나의 화면에서 쉽게 볼 수 있습니다. 그런데, 어떤 이유에서건 유사한 차트를 VB.Net 또는 C#으로 프로그래밍하려다 보면, 캔들차트와 보조지표를 동시에 출력하는 것만도 쉬운 작업이 아닙니다.

  뿐만 아니라, 마우스로 캔들을 가리킬 때마다 해당 캔들의 시가나 종가를 풍선 도움말로 출력하거나, 해당 날짜의 공시 또는 기사를 적절히 표현하는 등 사용자가 자연스럽게 요구하는 기능들을, 상용 컴포넌트나 라이브러리를 사용하지 않고, 아무것도 없는 밑바닥부터 개발하는 것은 정말 어려운 일이겠지요.
  

그림 1. 차트 샘플


  그렇더라도...? 그래서 '공유'가 중요하다는 생각을 합니다. 첫 번째 주제로, 캔들차트나 Column 차트 또는 Line 차트 등의 'y축 스케일'에 관한 문제을 풀어보도록 하겠습니다.


  • 상황: 차트에 출력해야할 데이터의 y축 값이 특정 이벤트에 의해 변한다.
  • 문제: y축 최대값 또는 최소값을 해당 이벤트에 따라 변경해 주어야 한다.


  
  위 그림 1은 우리가 흔히 볼 수 있는 캔들차트와 MACD 지표 및 기타 정보를 포함하는 차트로서 정적인 데이터만 표현하는 것처럼 보이지만, 사실은 동적인 이벤트에 따라 값이 달라지는 데이터 또한 출력합니다. 대표적인 이벤트는 마우스 포인터의 이동이나 스크롤바의 이동 등일 것입니다.
  
  이 글의 주제와 직접적인 연관이 있는 것이 스크롤바의 이동입니다. 차트 화면 크기의 제약으로 인해 실제 캔들의 수보다 훨씬 적은 갯수의 캔들만을 출력해야만 가시성이 좋은 경우가 대부분이며, 따라서 스크롤바를 사용하는 것이 일반적입니다. 참고로, 그림 1의 차트에 연결된 데이터소스는 다음 그림 2와 같습니다. 전체 데이터의 갯수, 즉 캔들의 수가 1,853 개인데, 이 모두를 한 화면에 담는다면 보기가 쉽지 않겠지요?
    

그림 2. 차트의 DataSource

  
  그런데, 원하는 부분을 찾기 위해 스크롤바를 움직일 때, 차트에 출력되지 않는 캔들을 제외하고, 실제로 차트에 출력되는 캔들의 고점과 저점에 맞추에 y축 값의 범위가 변하게끔 해야할 것입니다. 그렇게 하지 않고, 전체 캔들 중의 최고가와 최저가로 y축 범위를 고정시켜 버린다면, 차트가 화면의 위 또는 아래쪽에 치우쳐 가시성이 떨어지는 경우가 생깁니다. 반대로, 화면에 출력해야할 캔들의 고점 및 저점과는 상관없는 값으로 범위를 지정하면, 캔들이 차트에서 사라질 수도 있습니다.
  
  상황과 문제를 장황하게 서술했지만, 답은 간단합니다. 스크롤바를 움직일 때,

  1. 스크롤바의 위치를 구한다.
  2. 1의 결과를 가지고 전체 데이터 중에서 차트에 출력되어야할 데이터의 범위를 찾는다.
  3. 2에서 찾은 데이터, 즉 캔들들의 고점과 저점을 구한다.
  4. AxisY의 Maxumum과 Minumum 값을 3에서 찾은 값으로 지정한다.
  
  우선, 1 ~ 4에 필요한 문법(in VB.Net)을 소개하면 다음 그림과 같습니다.
  

그림 3. 관련 문법 (VB.Net)
  
  2 ~ 3(그림 3의 ②, ③)의 과정은 차트의 DataSource type이나 실제 데이터의 종류에 따라 달라지겠지만, 1 ~ 4의 맥락은 동일할 것입니다. 단, 위 그림 3의 샘플 소스는 보조지표가 포함되지 않은 캔들차트를 대상으로 한 것이며, ④의 내용 중 '0.05 * (mx - mn)'은 차트 상하에 약간의 여분을 두기 위함입니다.
  
  하나의 차트에 출력해야할 시리즈(계열, series)의 수가 위 샘플 소스에서 가정한 것처럼 한 개가 아니라 여러 개이면 어떻게 해야할까요? 두 가지의 경우를 고려해야 합니다.
  
  첫 번째 경우는 모든 시리즈의 YAxisType이 AxisType.Primary인 경우입니다. 즉, y축이 하나인 것이죠. 이 경우에는 단순하게 시리즈 고점들 중의 최고점과 시리즈 저점들 중의 최저점을 y축의 범위로 지정하면 그만입니다.
  
  두 번째 경우는 위 그림 1과 같이 AxisType.Primary와 AxisType.Secondary를 모두 사용할 때입니다. 캔들차트를 Primary축, 즉 AxisY에 대응시키고, 보조지표는 Secondary축 AxisY2에 대응시켰는데, 축 값의 scale이 서로 다릅니다. AxisY와 AxisY2의 Minimum 및 Maximum을 각각 어떤 값으로 지정해야 할까요?
  
  캔들차트나 보조지표 등 시리즈들을 어떻게 배치하는냐에 따라 달라질텐데요, 그림 1처럼 배치한다고 가정하겠습니다. 즉, 화면을 수직으로 5등분한 후, 보조지표를 제일 하단에 위치시키는 것입니다.
  

그림 4. Axis의 범위 지정
  
  Primary축, 즉 AxisY의 Maximum은 차트에 출력되는 캔들 중 최고점으로 지정합니다(ⓐ). 캔들 중 최저점을 구하고(ⓑ), 그 차이를 4로 나누면 그림 4의 h1값이 되겠지요? 그렇다면, AxisY의 Minimum값(그림 4의 m)은 'AxisY2.Minimum - h1'입니다.
  
  비슷한 방법으로 AxisY2의 Minimum과 Maximum값을 구할 수 있습니다. AxisY2.Minimum은 그림 4의 ⓒ처럼 대응시키면 되고,  M은 'AxisY2.Minimum + 5 * h2'로 지정하면 될 것입니다.
  
  샘플 소스는 다음 그림을 참고하시기 바랍니다. 상하 여백을 감안한 샘플입니다.
  

그림 5. 샘플 소스