축의 범위

축이 표현하는 범위는 들어오는 데이터에 따라 자동으로 결정하며 대개의 경우 차트가 알아서 설정하도록 내버려 두는 것이 무난하다. 우리의 예에서는 X축이 9시 정각 ~ 919분까지이고 Y축은 10 ~ 100까지의 점수값이다. 그래서 이 범위에 맞게 양축의 범위를 알아서 지정한다.

값의 범위에 너무 꼭 맞게 축범위를 설정하면 여유가 없어 약간의 여분을 더하는데 IsMarginVisible 속성으로 여분을 줄 것인가를 지정한다. 이 외에도 축의 범위를 설정하는 여러 가지 속성이 있다. Y값의 범위를 다음과 같이 바꾸어 보자.

 

for (int i = 0;i<20;i++)

{

       chart1.Series[0].Points.AddXY(dt, R.Next(10, 100) + 100);

       dt = dt.AddMinutes(1);

}

 

원래는 10~100까지였지만 여기에 100을 더해 110 ~ 200까지로 범위를 수평 이동시켰다.

값이 모두 위로 100만큼 수평 이동하여 아래쪽은 다 가득차 있는 모양이다. 100 이하의 값이 없는데 Y축의 범위를 0 ~ 200으로 잡는 것은 공간을 낭비한다. 만약 값이 1000 ~ 1100 사이에 분포한다면 낭비는 더 심해지고 시간별 값의 차이도 분명히 보이지 않을 것이다.

차트는 모든 값이 양수이면 범위의 시작을 항상 0부터 시작하도록 되어 있다. 값 중에 음수가 있다면 가장 작은 값을 시작값으로 잡는다. 이런 동작은 Is​Started​From​Zero 속성으로 제어하며 이 값의 디폴트가 true이다. 이 속성을 false로 변경하면 0이 아닌 적당한 시작점을 선정한다.

 

ay.IsStartedFromZero = false;

Y축의 시작점이 100으로 설정된다. 0을 시작점으로 하지 않고 실제 데이터가 존재하는 구간을 분석하여 적당한 범위를 잡는다. 110~200 사이이면 100 ~ 200 정도의 범위가 합당하다. IsMarginVisible 속성까지 false로 지정하면 정말 데이터가 실존하는 부분만 범위로 설정한다.

최소값인 113 ~ 최대값인 199까지 범위로 설정된다. 공간을 가득 채워서 알뜰하게 쓰기는 하지만 Y축의 레이블이 딱 떨어지지 않아 미관상 좋지 않고 X축의 제일 마지막인 113값은 아예 막대가 그려지지 않아 없는 것처럼 보인다.

축의 범위는 Axis 객체의 Minimum, Maximum 속성으로 조정한다. 두 속성 모두 디폴트는 NaN이며 차트가 알아서 결정하도록 되어 있다. 범위를 직접 지정하려면 두 속성에 원하는 최소값과 최대값을 지정한다. 값 생성문의 +100을 제거하여 10 ~ 100 사이의 값을 생성하고 Y축의 범위를 다음과 같이 직접 지정해 보자.

 

ay.Minimum = -20;

ay.Maximum = 200;

범위를 직접 지정하면 0부터 시작, 여백 등의 다른 모든 속성은 무시하고 지정한 최소값과 최대값의 범위를 강제로 적용한다. 최소값이 -20이다 보니 막대의 아래쪽은 비어 있고 최대값이 200이어서 100 이상의 공간도 비어 있어 시원스럽기는 하다. 값의 범위보다 더 좁게 설정할 수도 있다.

 

ay.Minimum = 30;

ay.Maximum = 50;

실제값보다 범위를 더 좁게 잡으면 아래 위가 잘린다. 30보다 작은 값은 모두 막대가 생략되어 실제값을 알 수 없으며 50보다 더 큰 값은 모두 같아 보인다. 문제가 있지만 어쨌거나 차트는 명령한 대로 범위를 표현할 뿐이다.

여러 개의 시리즈를 같은 에리어에 표시할 때 차트는 모든 시리즈의 범위를 포괄하는 공통의 범위를 잡는다. 공통 범위가 너무 넓어 특정 시리즈가 잘 보이지 않거나 특이값이 있는 시리즈로 인해 공간이 낭비된다면 적당한 수준의 범위를 강제 지정하는 편이 더 나을 수도 있다.

다음은 X축의 범위를 지정해 보자. Y축은 숫자여서 최소, 최대값을 지정하기 편리하지만 X축은 시간값이어서 단순한 숫자가 아니다. 원본 데이터는 9:00 ~ 9:19까지 있지만 이중 앞뒤는 잘라먹고 9:05~9:15까지만 범위를 설정해 보자.

 

ax.Minimum = new DateTime(2020, 10, 1, 9, 5, 0);

ax.Maximum = new DateTime(2020, 10, 1, 9, 15, 0);

 

이렇게 되면 쉽겠지만 이 코드는 에러이다. 왜냐하면 Minimum, Maximum 속성의 타입은 DateTime이 아니라 double이기 때문이다. X축이 시간으로 고정되어 있지 않고 순서값인 경우가 더 일반적이어서 double 타입으로 정의되어 있다.

X축의 한 지점도 double 타입으로 표현해야 하는데 이 때 사용하는 타입이 OADate이다. OADateOLE Automation에서 사용하던 타입이며 날짜를 실수 포맷으로 표현한다. 18991230일 자정을 0으로 잡고 경과한 날짜를 정수부에, 경과한 시간을 실수부에 저장한다.

시간이 날짜의 소수부인 것은 논리적으로 합당하다. OADate 타입에서 1은 하루이며 0.5는 반나절인 12시간이다. 하루가 86400초이며 고작 십진수 다섯자리에 불과해 double의 정밀도이면 충분히 긴 날짜와 충분히 정밀한 시간을 표현할 수 있다. DateTime 클래스는 OADate 타입으로 변환하는 메서드를 제공한다.

 

public static DateTime FromOADate (double d);

public double ToOADate ();

 

FromOADate는 실수값으로부터 DateTime 객체를 만든다. 새로 객체를 만들어야 하므로 static이다. ToOADate는 반대로 DateTime 객체를 double 타입으로 변환한다. 이 메서드를 사용하면 원하는 시간을 정확히 지정할 수 있다.

 

ax.Minimum = (new DateTime(2020, 10, 1, 9, 5, 0)).ToOADate();

ax.Maximum = (new DateTime(2020, 10, 1, 9, 15, 0)).ToOADate();

 

20201019:5double 타입으로 44105.3784이다. 1899년으로부터 44105일이 지났으며 하루 24시간의 대략 37%쯤의 시간이라는 뜻이다. 차트는 X축의 시간을 이 타입으로 저장한다. 따라서 범위를 지정할 때도 double 타입으로 바꿔서 지정해야 한다. 결과는 다음과 같다.

X축의 9:05~9:15까지만 표시되며 나머지는 잘려 보이지 않는다. 물론 더 넓게 잡을 수도 있다. Minimum은 디폴트로 두고 Maximum10시 정각으로 조정해 보자.

 

ax.Maximum = (new DateTime(2020, 10, 1, 10, 0, 0)).ToOADate();

뒤쪽은 데이터가 없어 비어 있지만 어쨌거나 지정한 범위만큼 정확히 표시된다. 시간을 double 타입으로 변환하는 방법만 알면 Y축의 범위를 지정하는 방법과 같다.

앞 항에서 그리드와 틱마크를 실습할 때 주로 Y축만을 대상으로 했는데 X축의 한 지점을 기술하는 방법을 몰랐기 때문이다. X축도 시간값을 실수로 변환해서 지정하면 Y축과 방법은 같다.