데이터

차트의 구조

그래프 하나를 그리려면 여러 개의 값이 모여 시리즈를 구성해야 한다. 여러 개의 시리즈를 하나의 에리어에 같이 그릴 수 있고 차트 컨트롤은 에리어 여러 개를 동시에 표현할 수 있다. 이런 식이다 보니 차트를 구성하는 모든 것은 컬렉션이다. 차트 컨트롤을 선택해 놓고 속성창을 범주별로 나열해 보면 다섯 개의 컬렉션이 있다.

이 컬렉션을 어떻게 구성하는가에 따라 차트의 모양과 기능이 결정된다. 각 컬렉션이 차트에 어떤 식으로 나타나는지 보자.

Chart 컨트롤 전체 영역 chart picture라고 하며 이 안에 여러 개의 Area, legend, title이 배치된다. Area안에는 데이터 컬렉션인 시리즈와 그리드, , 틱 마크, 레이블 등이 배치된다. Area는 가장 자리의 축과 중앙의 Plot 영역으로 나누어진다.

Plot 영역 안에 시리즈를 그래픽 형태로 표시한 그래프와 그리드가 출력되고 그 바깥에 X, Y 축과 틱 마크, 축 타이틀, 레이블 등이 표시된다. 축은 Area에 소속된 Axes 컬렉션이며 상하 X2, 좌우 Y2개로 구성된다. 디폴트로 X축은 아래쪽만 표시되어 있고 Y축은 왼쪽만 표시되어 있다.

차트를 구성하는 모든 것은 컬렉션이며 컬렉션끼리 서로 소속되고 연결되는 식이다. 차트 안에 여러 개의 에리어가 있고 에리어안에 여러 개의 시리즈가 있으며 시리즈 안에 또 여러 개의 값이 저장된다. 이 구조에 익숙해져야 원하는 요소를 신속, 정확하게 찾을 수 있다.

데이터

시리즈는 차트에 출력할 데이터값의 집합이다. 예를 들어 성적표를 그린다면 각 학생의 점수 데이터 집합이 하나의 시리즈가 된다. 데이터 집합은 SeriesPoints 컬렉션으로 관리한다.

 

DataPointCollection Points

 

DataPointCollectionDataPoint의 컬렉션이며 DataPoint는 시리즈를 구성하는 데이터 하나이다. 즉 차트를 구성하는 가장 원자적인 단위가 바로 DataPoint이다. 하나의 데이터는 다음 두 값으로 구성된다.

 

XValue : 가로 X축에 표시할 값이며 타입은 double이다. 성적표라면 학생의 출석 번호가 X값으로 사용된다. X값이 반드시 있어야 하는 것은 아니며 생략시 컬렉션에 추가한 순서대로 그려 연속적인(non scatter) 차트가 된다. X값을 지정하면 이 값에 따라 비 연속적인 흩어진(scatter) 차트가 된다.

YValues : 세로 Y축에 표시할 값이다. 성적표라면 각 학생의 점수값이다. 보통은 하나의 값만 있지만 여러 개의 값을 조합하여 보여 주는 차트는 Y값이 여러 개일 수도 있다. 예를 들어 주식 시황을 그리는 차트는 시가, 종가, 고가, 저가 등 4개의 값을 한꺼번에 그린다. 그래서 이 속성의 타입은 double []이다.

 

차트의 형태가 워낙 다양하다 보니 X값은 없을 수도 있고 Y값이 여러 개일 수도 있다. DataPointCollectionDataPoint를 추가하는 메서드를 제공한다. 디자이너에서도 데이터를 직접 추가할 수 있지만 대량 추가는 아무래도 코드를 사용해야 한다. 차트의 구조를 살펴 볼 수 있는 가장 원론적인 메서드이므로 자세히 살펴 보자. Y값만 추가할 때는 다음 두 메서드를 사용한다.

 

int AddY (double yValue);

int AddY (params object[] yValue);

 

첫 번째 메서드는 하나의 y값을 컬렉션의 마지막에 추가하고 그 첨자를 리턴한다. 성적을 순서대로 나열한다면 첫 학생의 성적부터 순서대로 죽 추가하면 된다.

 

private void Form1_Load(object sender, EventArgs e)

{

       chart1.Legends[0].Enabled = false;

       chart1.Series[0].Points.AddY(55);

       chart1.Series[0].Points.AddY(77);

       chart1.Series[0].Points.AddY(66);

}

 

당분간 범례는 필요없으므로 제거한 상태로 실행해 보자. 안그래도 지면이 좁은데 범례까지 있으면 차트를 크게 볼 수 없어 범례를 숨기기로 한다.

Y값만 주면 자동으로 1, 2, 3번 학생의 성적이 된다. object[]을 받는 AddY 메서드는 여러 개의 Y값을 받을 수도 있고 여러 타입의 값을 받을 수도 있다. 그렇다고 해서 아무 타입이나 다 받는 것은 아니며 정수, 실수 타입과 String, DateTime 정도만 가능하다. 점수값이 문자열 형태로 조사되었다면 그냥 문자열을 Add 해도 된다.

 

chart1.Series[0].Points.AddY("55");

chart1.Series[0].Points.AddY("77");

chart1.Series[0].Points.AddY("66");

 

이렇게 해도 결과는 같다. 문자열 안에 저장된 정수, 실수를 double 타입으로 바꿔서 사용한다. 일반적이지 않지만 DateTime 타입도 대소 구분이 가능하므로 Y값으로 쓸 수 있다.

 

chart1.Series[0].Points.AddY(DateTime.Now);

chart1.Series[0].Points.AddY(DateTime.Now.AddDays(1));

chart1.Series[0].Points.AddY(DateTime.Now.AddDays(2));

Y축에 날짜가 나타난다. 다음은 여러 개의 Y값을 가지는 차트를 그려 보자. 주식 차트에 주로 사용하는 캔들스틱이 대표적이다. 차트 타입을 먼저 바꿔야 하며 커스텀 프로피터를 사용하여 상승은 빨간색, 하락은 파란색으로 지정했다. X축 그리드는 없애는 것이 보기 좋다.

 

chart1.Series[0].ChartType = SeriesChartType.Candlestick;

chart1.Series[0].CustomProperties = "PriceDownColor=Blue, PriceUpColor=Red";

chart1.ChartAreas[0].AxisX.MajorGrid.Enabled = false;

chart1.Series[0].Points.AddY(900, 1600, 1000, 1500);

chart1.Series[0].Points.AddY(1000, 1700, 1200, 1600);

chart1.Series[0].Points.AddY(1200, 500, 1000, 600);

이런 차트를 그리려면 X축 하나에 대해 여러 개의 Y값이 필요하다. AddY 메서드는 X축값을 모두 생략한다. 다음 두 메서드는 X축을 지정하여 좀 더 다양한 형태의 차트를 그린다.

 

public int AddXY (double xValue, double yValue);

public int AddXY (object xValue, params object[] yValue);

 

하나의 y값을 받는 버전과 여러 개의 y값을 받는 버전이 있다. 1, 2,3 번 학생의 성적표를 그리고 싶다면 X값에 출석 번호를 적어 준다.

 

chart1.Series[0].Points.AddXY(1, 55);

chart1.Series[0].Points.AddXY(2, 77);

chart1.Series[0].Points.AddXY(3, 66);

 

이렇게 하면 X를 생략하고 세 개의 Y값을 순서대로 제공하는 것과 마찬가지이며 굳이 X값을 줄 필요가 없다. 그러나 X가 비연속적이거나 다른 타입일 때는 X값을 지정해야 한다. 예를 들어 3번 학생이 전학을 가서 없다고 하면 1, 2, 4번을 X값으로 지정한다.

 

 

chart1.Series[0].Points.AddXY(1, 55);

chart1.Series[0].Points.AddXY(2, 77);

chart1.Series[0].Points.AddXY(4, 66);

3번 자리는 빈 것으로 나타난다. 또 출석 번호가 아닌 각 학생의 이름으로 성적을 출력할 때도 X값을 지정한다.

 

chart1.Series[0].Points.AddXY("철수", 55);

chart1.Series[0].Points.AddXY("영희", 77);

chart1.Series[0].Points.AddXY("동수", 66);

학생 이름은 XValue에 저장되는 것이 아니라 DataPoint 객체의 AxisLabel 속성에 저장된다. XValue는 항상 숫자이다. X축에 시간을 표시할 때는 DateTime 타입을 사용한다. 다음 차트는 15분간의 값 변화를 표현한다.

 

Random R = new Random(100);

DateTime end = DateTime.Now.AddMinutes(15);

for (DateTime dt = DateTime.Now;dt < end;dt = dt.AddMinutes(1))

{

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

}

편의상 값은 난수로 생성했다. X축에는 시간이 표시되는데 날짜, 시간도 double 타입으로 변환되어 저장된다. 마지막 Add 메서드는 한 개 이상의 y값을 추가하고 DataPoint 객체를 리턴한다.

 

DataPoint Add (params double[] y);

 

다른 Add 메서드가 삽입한 위치를 리턴하는데 비해 이 메서드는 DataPoint 객체를 리턴한다는 점이 특이하다. 추가 후 리턴된 DataPoint 객체로 여러 가지 속성을 편집할 수 있다.

다음 메서드는 컬렉션의 데이터를 관리한다. 추가하는 기능이 있으면 당연히 삽입, 삭제, 검색, 초기화 등의 메서드도 필요하다. 컬렉션이 갖추어야 할 모든 기능이 다 구비되어 있다.

 

public void InsertY (int index, params object[] yValue);

public void RemoveAt (int index);

public void Clear ();

 

메서드 이름만 봐도 기능을 쉽게 유추할 수 있다. 편의상 여기서는 대표 원형만 보이는데 InsertY가 있으면 응당 InsertXYInsert도 있기 마련이다. 객체 지향 라이브러리는 필요할 거 같은 메서드는 거의 다 제공하므로 항상 레퍼런스를 참조하자. 다음 코드는 3개의 데이터를 추가한 후 1번 위치에 88을 삽입하고 2번 위치의 77 데이터를 제거한다.

 

chart1.Series[0].Points.AddY(55);

chart1.Series[0].Points.AddY(77);

chart1.Series[0].Points.AddY(66);

chart1.Series[0].Points.InsertY(1, 88);

chart1.Series[0].Points.RemoveAt(2);

하나를 넣고 하나를 뺐으니 결국 세 개 남는다. 데이터를 완전히 새로 입력하고 싶으면 Clear로 전부 제거한 후 다시 추가한다. 다음 메서드는 컬렉션에서 데이터를 찾는다.

 

DataPoint FindByValue (double valueToFind);

DataPoint FindByValue (double valueToFind, string useValue);

DataPoint FindByValue (double valueToFind, string useValue, int startIndex);

IEnumerable<DataPoint> FindAllByValue (double valueToFind);

DataPoint FindMinByValue ();

DataPoint FindMaxByValue ();

 

FindByValue는 최대 3개의 인수를 가진다. valueToFind는 찾을 값이다. useValue는 이 값이 X인지 Y1인지를 지정하며 생략시 Y값을 찾는다. startindex는 검색을 시작할 위치이되 생략시 처음부터 찾는다. 검색된 위치 다음부터 검색하면 순서대로 값을 찾을 수 있다.

모든 값을 한꺼번에 다 찾을 수도 있고 최소, 최대값을 찾을 수도 있다. 찾는 값이 있으면 해당 DataPoint 객체를 리턴하며 없으면 null을 리턴한다. 꼭 값이 있다고 보장할 수 없으므로 null 점검은 반드시 해야 한다. 다음 코드는 77점짜리 점수를 찾아 22점으로 수정한다.

 

chart1.Series[0].Points.AddY(55);

chart1.Series[0].Points.AddY(77);

chart1.Series[0].Points.AddY(66);

DataPoint dp = chart1.Series[0].Points.FindByValue(77);

if (dp != null)

{

    dp.YValues = new double[] { 22 };

}

Series의 속성이 Points 컬렉션은 데이터를 추가, 삭제, 삽입, 검색 등 모든 관리 기능을 다 지원한다. 원하는 모든 것을 다 할 수 있어 자유도가 높고 유연하다.

DataPoint

Points 컬렉션의 한 요소인 DataPoint도 객체이다. 직접 객체를 생성하여 컬렉션에 추가할 수도 있고 여러 가지 속성을 조정하여 다양한 모양을 만들 수도 있다. 아주 많은 속성이 있는데 다음은 자주 사용하는 속성이다.

 

속성

설명

AxisLabel

X축에 표시할 문자열이다. 디폴트로 순서값이 들어가는데 임의의 문자열을 표시할 수 있다.

Color

배경색이다. 디폴트는 시리즈의 배경색을 상속받는다.

BorderColor

경계색이다.

BorderWidth

경계선의 굵기이다.

BorderDashStyle

경계선의 모양이다.

Font

글꼴을 지정한다.

Label

막대 위에 표시할 문자열이다.

MakerStyle

막대 위에 표시할 도형이다. , 십자, 삼각형, 사각형,

MakerSize

마커의 크기를 지정한다.

ToolTip

마우스 커서가 머무르면 나타나는 툴팁이다.

XValue

X축 값이다.

YValues

Y축 값의 배열이다.

 

두 번째 데이터의 속성을 바꿔 보자.

 

chart1.Series[0].Points.AddY(55);

chart1.Series[0].Points.AddY(77);

chart1.Series[0].Points.AddY(66);

DataPoint dp = chart1.Series[0].Points[0];

dp.AxisLabel = "철수의 점수";

dp.Color = Color.Yellow;

dp.BorderColor = Color.Red;

dp.BorderWidth = 3;

dp.Label = "꼴찌";

dp.MarkerStyle = MarkerStyle.Star5;

dp.MarkerSize = 25;

dp.ToolTip = "철수의 시험 점수입니다.";

이 외에도 수 많은 속성이 있다. 막대 그래프 안에 이미지를 출력할 수도 있고 그래디언트 배경으로 채울 수 있으며 레이블의 포맷이나 속성까지도 마음대로 조정할 수 있다. 심지어 차트 타입별로 커스텀 속성까지 지정할 수 있다.

개별 데이터 하나에 대한 속성의 상세도가 이 정도이니 시리즈나 에리어의 속성은 어느 정도일지 가히 짐작이 갈 것이다. 상상할 수 있는 거의 모든 형태를 다 지원한다.

데이터 바인딩

Add* 메서드는 Y값이 여러 개일지라도 어쨌거나 하나의 데이터만 추가하며 X축의 값 개수만큼 메서드를 일일이 호출해야 한다. 반면 데이터 바인딩은 컬렉션으로부터 여러 개의 데이터를 일괄 추가한다.

 

void DataBindY (params IEnumerable[] yValue);

void DataBindY (IEnumerable yValue, string yFields);

 

yValue 인수는 열거 가능한 타입이며 DataView, Data reader, Array, List 등을 사용할 수 있다. 대량의 데이터는 주로 DB에서 읽어들인다. DB의 데이터는 실행중에 연결하여 읽어야 하므로 디자인 타임에는 바인딩을 할 수 없으며 런타임에 코드로만 할 수 있다.

각 컬렉션에서 값을 읽어 일괄 추가하되 첫 번째 메서드는 첫 컬럼을 읽고 두 번째 메서드는 yFields로 지정한 컬럼을 읽는다. 가장 쉬운 방법은 배열에서 읽는 것이다.

 

chart1.Series[0].Points.DataBindY(new int[] { 55, 77, 66 });

 

세 개의 성적값이 한꺼번에 등록된다. 다음 메서드는 X값도 같이 지정한다.

 

public void DataBindXY (IEnumerable xValue, params IEnumerable[] yValues);

public void DataBindXY (IEnumerable xValue, string xField, IEnumerable yValue, string yFields);

 

X 배열을 지정하면 학생의 이름도 한줄에 다 지정할 수 있다.

 

chart1.Series[0].Points.DataBindXY(new string[] { "철수", "영희", "동수" }, new int[] { 55, 77, 66 });

 

배열보다는 데이터베이스가 더 일반적이다. 얼마든지 많은 데이터를 출력할 수 있다. 메모리상에서 테이블을 만든 후 연결해 보자.

 

private void Form1_Load(object sender, EventArgs e)

{

    chart1.Legends[0].Enabled = false;

    DataTable tAge = MakePeopleTable();

    chart1.Series[0].Points.DataBindY(tAge.Rows, "Age");

}

 

private DataTable MakePeopleTable()

{

    DataTable tblPeople = new DataTable("tblPeople");

 

    DataColumn col;

    DataRow row;

 

    // 열 등록

    col = new DataColumn("Name", typeof(String));

    col.MaxLength = 10;

    col.AllowDBNull = false;

    col.Unique = true;

    tblPeople.Columns.Add(col);

 

    tblPeople.PrimaryKey = new DataColumn[] { col };

 

    col = new DataColumn("Age", typeof(Int32));

    col.AllowDBNull = false;

    tblPeople.Columns.Add(col);

 

    col = new DataColumn("Male", typeof(bool));

    col.AllowDBNull = false;

    tblPeople.Columns.Add(col);

 

    // 행 삽입

    row = tblPeople.NewRow();

    row["Name"] = "정우성";

    row["Age"] = 36;

    row["Male"] = true;

    tblPeople.Rows.Add(row);

 

    row = tblPeople.NewRow();

    row["Name"] = "고소영";

    row["Age"] = 32;

    row["Male"] = false;

    tblPeople.Rows.Add(row);

 

    row = tblPeople.NewRow();

    row["Name"] = "배용준";

    row["Age"] = 37;

    row["Male"] = true;

    tblPeople.Rows.Add(row);

 

    row = tblPeople.NewRow();

    row["Name"] = "김태희";

    row["Age"] = 29;

    row["Male"] = false;

    tblPeople.Rows.Add(row);

 

    tblPeople.AcceptChanges();

 

    return tblPeople;

}

DataTable 자체는 열거 가능한 값이 아니므로 tAge.Rows를 지정해야 한다. 4명의 나이가 나타난다. Age 필드만 사용했는데 이름도 표시할 수 있다.

 

chart1.Series[0].Points.DataBindXY(tAge.Rows, "Name", tAge.Rows, "Age");

X값과 Y값의 테이블이 꼭 일치하지 않아도 상관 없다. 마지막 함수는 다음과 같다.

 

public void DataBind (IEnumerable dataSource, string xField, string yFields, string otherFields);

 

한 테이블에서 X, Y값을 다 구하며 마지막 인수로 바인딩 룰을 규정하는 필드를 지정한다. 다음은 앞 코드와 결과가 같다.

 

chart1.Series[0].Points.DataBind(tAge.Rows, "Name", "Age", "");

 

메모리상에서 만든 테이블이 아닌 DBMS에서 읽은 테이블을 바인딩할 수도 있다. 이 실습을 해 보려면 SQL 서버(또는 다른 DBMS)가 설치되어 있어야 하고 SQL을 어느 정도 할 수 있어야 한다. 차트의 속성창에서 DataSource 속성 팝업을 열고 데이터 소스 추가를 선택한다.

 

데이터베이스를 선택하고 로컬 호스트에 대한 연결을 생성한다. 강좌 제작 PC에는 Study 데이터베이스에 여러 개의 실습 테이블이 준비되어 있는데 여러분은 SQL 서버로 대충 아무 테이블이나 만들어 놓고 실습해 보자.

이 중 도서의 정보를 저장하고 있는 tCity 테이블을 선택한다. 우리나라 도시의 인구, 면적 등에 대한 정보가 저장되어 있다.

여기까지 처리한 후 Form1_Load에 다음 코드를 작성한다.

 

private void Form1_Load(object sender, EventArgs e)

{

       // TODO: 이 코드는 데이터를 'studyDataSet.tCity' 테이블에 로드합니다. 필요 시 이 코드를 이동하거나 제거할 수 있습니다.

       this.tCityTableAdapter.Fill(this.studyDataSet.tCity);

       chart1.Series[0].Points.DataBind(studyDataSet.tCity, "Name", "Area", "");

}

 

첫 줄은 마법사가 작성해 준 것이며 이 문장에 의해 studyDataSet.tCity 테이블에 데이터가 이미 읽혀져 있다. DataBind 메서드로 연결만 해 주면 된다. 마법사가 만든 테이블은 타입드여서 바로 열거 가능하다. Name 필드를 X축으로 하고 AreaY축으로 하면 도시별 면적이 차트로 출력된다.

Y축 필드를 Popu로 변경하면 도시별 인구수를 차트로 보여준다. DataBind 메서드 대신 시리즈의 각 속성에 테이블과 X 필드, Y 필드를 지정해도 결과는 같다.

 

private void Form1_Load(object sender, EventArgs e)

{

    // TODO: 이 코드는 데이터를 'studyDataSet.tCity' 테이블에 로드합니다. 필요 시 이 코드를 이동하거나 제거할 수 있습니다.

    this.tCityTableAdapter.Fill(this.studyDataSet.tCity);

    chart1.DataSource = studyDataSet.tCity;

    chart1.Series[0].XValueMember = "Name";

    chart1.Series[0].YValueMembers = "Area";

}

 

데이터베이스와 연결할 수 있어 얼마든지 다양한 소스로부터 데이터를 받아 차트로 그릴 수 있다. 다만 이 실습은 차트에 대한 지식보다 데이터베이스에 대한 지식이 더 많이 필요해 ADO.NET을 충분히 연구해 봐야 한다.

차트 타입

차트의 타입은 시리즈의 데이터를 어떤 방식으로 보여줄 것인가를 지정한다. 35가지의 차트 타입이 있다. 다 찍어서 눈으로 확인해 보자. 기존 실습 프로젝트와는 용도가 조금 다르므로 charttype 새 프로젝트를 만든다. 루프를 돌며 차트 컨트롤을 만들고 75행으로 나열하면 된다. 4개의 정수값을 주고 모양을 분명히 살펴 보기 위해 그리드는 제거하였다.

 

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

using System.Windows.Forms;

using System.Windows.Forms.DataVisualization.Charting;

 

namespace charttype {

        public partial class Form1 : Form {

                   public Form1()

                   {

                              InitializeComponent();

                   }

 

                   Chart[] arChart = new Chart[35];

                   SeriesChartType[] arType = new SeriesChartType []

                   {

                              SeriesChartType.Column, SeriesChartType.Bar, SeriesChartType.Line,

                              SeriesChartType.StepLine, SeriesChartType.Spline, SeriesChartType.Area,

                           SeriesChartType.SplineArea,SeriesChartType.Range,SeriesChartType.SplineRange,

                              SeriesChartType.RangeBar, SeriesChartType.RangeColumn, SeriesChartType.Pie,

                              SeriesChartType.Doughnut, SeriesChartType.Pyramid ,SeriesChartType.Funnel,

                              SeriesChartType.ThreeLineBreak, SeriesChartType.PointAndFigure ,SeriesChartType.Polar,

                              SeriesChartType.Radar, SeriesChartType.BoxPlot, SeriesChartType.Point,

                              SeriesChartType.Bubble, SeriesChartType.Stock, SeriesChartType.Candlestick,

                              SeriesChartType.ErrorBar, SeriesChartType.Kagi, SeriesChartType.Renko,

                              SeriesChartType.StackedArea, SeriesChartType.StackedArea100, SeriesChartType.StackedBar,

                              SeriesChartType.StackedBar100,SeriesChartType.StackedColumn,

                              SeriesChartType.StackedColumn100, SeriesChartType.FastLine, SeriesChartType.FastPoint

                   };

 

                   private void Form1_Load(object sender, EventArgs e)

                   {

                              int i = 0;

                              for (int y=0;y<5;y++)

                              {

                                        for (int x = 0; x < 7; x++)

                                        {

                                                   arChart[i] = new Chart();

                                                   arChart[i].Left = x * 190;

                                                   arChart[i].Top = y * 170;

                                                   arChart[i].Width = 180;

                                                   arChart[i].Height = 160;

                                                   arChart[i].ChartAreas.Add("main");

                                                   arChart[i].ChartAreas[0].AxisX.MajorGrid.Enabled = false;

                                                   arChart[i].ChartAreas[0].AxisY.MajorGrid.Enabled = false;

                                                   arChart[i].Series.Add("main");

                                                   arChart[i].Series[0].Points.DataBindY(new int[] { 22, 44, 33, 55 });

                                                   arChart[i].Series[0].Points[0].Color = Color.Blue;

                                                   arChart[i].Series[0].Points[1].Color = Color.Red;

                                                   arChart[i].Series[0].Points[2].Color = Color.Green;

                                                   arChart[i].Series[0].Points[3].Color = Color.Purple;

                                                   arChart[i].Series[0].ChartType = arType[i];

                                                   arChart[i].Titles.Add("");

                                                   arChart[i].Titles[0].Text = arType[i].ToString();

                                                   Controls.Add(arChart[i]);

                                                   i++;

                                        }

                              }

                   }

        }

}

 

이럴 때는 디자이너로 일일이 클릭해서 속성 조정하는 것보다 역시 코드가 편리하다. 위쪽 타이틀에 차트 타입을 표시했다.

 

 

대충만 관찰해 봐도 각 타입이 데이터를 어떻게 그리는지 알 수 있다. 똑같아 보이는 타입도 있지만 이는 데이터가 같아서 당장 같아 보일 뿐이며 여러 개의 데이터를 주면 달라질 수도 있다. 각 타입의 특징을 잘 알아 두고 목적에 딱 맞는 타입을 잘 골라야 한다.

개별 차트 타입도 다양하지만 여러 타입을 하나의 에리어에 같이 표시하면 더 다양한 형태의 차트를 그릴 수 있다. 그러나 모든 타입을 동시에 표시할 수 있는 것은 아니다. 대표적으로 ColumnBar는 축의 방향이 달라 같이 표시할 수 없으며 예외로 처리된다.

또한 PiePyramid 처럼 전체 영역을 다 사용하는 타입은 단독으로만 표시할 수 있다. 두 개 이상의 차트를 한 에리어에 같이 표시해 보자. 시리즈를 겹칠 때의 효과는 차트 타입에 따라 다르다. chartTest 예제에 코드를 작성하되 이번에는 범례를 살려 두자.

 

- 나란히 그리기(Cluster) : 하나씩 순서대로 그린다. Column이나 Bar 타입의 시리즈를 여러 개 표시할 때 이런 형태가 된다. 각 학생에 대해 세 과목의 성적을 동시에 그리려면 다음과 같이 한다.

 

private void Form1_Load(object sender, EventArgs e)

{

       chart1.Series[0].Name = "국어";

       chart1.Series[0].Points.DataBindY(new int[] { 22, 44, 33, 55 });

       chart1.Series[0].ChartType = SeriesChartType.Column;

       chart1.Series.Add("영어");

       chart1.Series[1].Points.DataBindY(new int[] { 40, 30, 60, 20 });

       chart1.Series[1].ChartType = SeriesChartType.Column;

       chart1.Series.Add("수학");

       chart1.Series[2].Points.DataBindY(new int[] { 50, 40, 20, 30 });

       chart1.Series[2].ChartType = SeriesChartType.Column;

}

디자인 타임에 배치한 차트는 Series[0] 하나만 자동 생성해 주므로 더 필요한 시리즈는 직접 만들어야 한다. 각 막대의 색상은 개별적으로 지정할 수 있다.

- 포개기(Overlap) : 같은 공간에 겹쳐서 그린다. 순서값이 빠른 시리즈를 먼저 그리므로 겹치는 부분은 순서값이 높은 시리즈가 위쪽에 온다.

 

private void Form1_Load(object sender, EventArgs e)

{

       chart1.Series[0].Name = "국어";

       chart1.Series[0].Points.DataBindY(new int[] { 22, 44, 33, 55 });

       chart1.Series[0].ChartType = SeriesChartType.Column;

       chart1.Series.Add("영어");

       chart1.Series[1].Points.DataBindY(new int[] { 40, 30, 60, 20 });

       chart1.Series[1].ChartType = SeriesChartType.Line;

       chart1.Series[1].BorderWidth = 4;

       chart1.Series.Add("수학");

       chart1.Series[2].Points.DataBindY(new int[] { 50, 40, 20, 30 });

       chart1.Series[2].ChartType = SeriesChartType.Spline;

       chart1.Series[2].BorderWidth = 4;

}

영어 성적은 Line 타입으로, 수학 성적은 Spline 타입으로 표시했다. 선이 얇으면 잘 보이지 않아 굵기를 4로 두텁게 칠했다. 영어와 수학이 겹치는 부분에는 순서가 뒤쪽인 수학 그래프가 더 위쪽에 있음을 유의하자. 국어는 물론 제일 아래쪽에 있다. 이 순서를 조정하려면 시리즈의 순서를 바꿔야 한다.

 

- 쌓기(Statck) : 한 시리즈 위쪽에 다음 시리즈의 값을 얹어서 보여 준다. 여러 값의 총합을 한눈에 파악할 수 있어 편리하다.

 

private void Form1_Load(object sender, EventArgs e)

{

       chart1.Series[0].Name = "국어";

       chart1.Series[0].Points.DataBindY(new int[] { 22, 44, 33, 55 });

       chart1.Series[0].ChartType = SeriesChartType.StackedColumn;

       chart1.Series.Add("영어");

       chart1.Series[1].Points.DataBindY(new int[] { 40, 30, 60, 20 });

       chart1.Series[1].ChartType = SeriesChartType.StackedColumn;

       chart1.Series[1].BorderWidth = 4;

       chart1.Series.Add("수학");

       chart1.Series[2].Points.DataBindY(new int[] { 50, 40, 20, 30 });

       chart1.Series[2].ChartType = SeriesChartType.StackedColumn;

       chart1.Series[2].BorderWidth = 4;

}

각 학생의 총점을 비교할 수 있고 한 학생의 국어, 영어, 수학 성적 비율도 쉽게 가늠할 수 있다. StackedBar 타입으로 바꾸면 학생 번호가 Y축으로 이동하고 성적이 X축으로 이동하여 가로 모양의 막대 그래프가 된다.  

StackedColumn100 타입으로 바꾸면 전체를 100으로 한 비율을 보여 준다.

스택드 타입은 시리즈 여러 개를 한꺼번에 보여주는 방식이다. 시리즈만 여러개일 뿐 각 시리즈의 Y값은 하나뿐이다. 이에 비해 시리즈 하나에 여러 개의 Y값이 들어가는 타입도 있다. 앞에서 잠시 구경해 본 CandleStick 타입이 멀티 Y 값을 사용하는 대표적인 예이며 Stock 타입도 구조가 비슷하다.

 

private void Form1_Load(object sender, EventArgs e)

{

       chart1.Series[0].ChartType = SeriesChartType.Stock;

       chart1.Series[0].CustomProperties = "OpenCloseStyle=Triangle";

       chart1.ChartAreas[0].AxisX.MajorGrid.Enabled = false;

       chart1.Series[0].Points.AddY(900, 1600, 1000, 1500);

       chart1.Series[0].Points.AddY(1000, 1700, 1200, 1600);

       chart1.Series[0].Points.AddY(1200, 500, 1000, 600);

}

주식 시세는 시가, 종가, 고가, 저가 4개의 값으로 구성되며 X값 하나에 대해 4개의 Y값이 있어야 제대로 표현된다. 여러 개의 Y값을 가지는 시리즈와 단일 값을 가지는 시리즈를 같은 에리어에 표현할 수도 있다. 예를 들어 위쪽에서는 주식 시세를 캔들 차트로 그리고 아래쪽에는 거래량을 그리면 된다.

 

private void Form1_Load(object sender, EventArgs e)

{

       chart1.Series[0].ChartType = SeriesChartType.Candlestick;

       chart1.Series[0].CustomProperties = "PriceDownColor=Blue, PriceUpColor=Red";

       chart1.ChartAreas[0].AxisX.MajorGrid.Enabled = false;

       chart1.Series[0].Points.AddY(900, 1600, 1000, 1500);

       chart1.Series[0].Points.AddY(1000, 1700, 1200, 1600);

       chart1.Series[0].Points.AddY(1200, 500, 1000, 600);

 

       chart1.Series.Add("거래량");

       chart1.Series[1].Points.DataBindY(new int[] { 400, 350, 280 });

       chart1.Series[1].ChartType = SeriesChartType.Column;

}

여기에 Line 차트로 이동 평균선까지 그려 넣으면 그럴 듯한 주식 차트를 만들 수 있다. 하지만 이 경우는 가격과 거래량의 단위가 달라 좀 억지스럽다. 가격은 몇천원인데 거래량은 몇만주이면 가격이 거래량에 가려 보이지도 않을 것이다. 두 값을 동시에 꼭 같이 표시하려면 에리어를 하나 더 만드는 것이 합당하다.

 

private void Form1_Load(object sender, EventArgs e)

{

       chart1.Series[0].ChartType = SeriesChartType.Candlestick;

       chart1.Series[0].CustomProperties = "PriceDownColor=Blue, PriceUpColor=Red";

       chart1.ChartAreas[0].AxisX.MajorGrid.Enabled = false;

       chart1.Series[0].Points.AddY(900, 1600, 1000, 1500);

       chart1.Series[0].Points.AddY(1000, 1700, 1200, 1600);

       chart1.Series[0].Points.AddY(1200, 500, 1000, 600);

 

       chart1.ChartAreas.Add("거래량");

       chart1.Series.Add("거래량");

       chart1.Series[1].Points.DataBindY(new int[] { 40000, 35000, 28000 });

       chart1.Series[1].ChartType = SeriesChartType.Column;

       chart1.Series[1].ChartArea = "거래량";

}

두 에리어의 높이 비율은 얼마든지 조정할 수 있고 위쪽 에리어의 X축은 아예 없애 버릴 수도 있다. 아니면 각 시리즈의 Y축을 서로 다르게 만들 수도 있다. 시리즈를 조합하는 방식만 해도 엄청나게 다양한데 에리어까지 분리할 수 있어 차트의 표현력이 무궁무진하다.