C#, WPF 무료 3D Chart Plotly.NET 사용

이 Post 는 Plotly.js 의 .NET Wrapper 인 Plotly.NET 의 사용 방법을 설명 합니다.
또한 C# 혹은 WPF 의GUI 에서 WebView2 를 사용해 Plotly.NET 으로 만든 그래프를 표시하는 방법 또한 설명합니다.

Plotly.js 라는 그래프를 그려주는 라이브러리가 있습니다.
JavaScript 로 사용할 수 있으며 Web 에서 쉽게 그래프를 그릴 수 있는 라이브러리 입니다.
.Net F# 에서도 Plotly.NET 이라는 Wrapper 가 있어 쉽게 사용할 수 있으며 Plotly.NET.CSharp 라는 Wrapper 가 있어 C# 으로도 쉽게 사용 할 수 있습니다.
……. 사실 Plotly.NET.CSharp 까지는 별로 필요 없이 Plotly.NET 만으로도 C#, WPF 에서 충분히 사용 가능 합니다.

Plotly.NET 은 그래프를 js 기반으로 작성한 후 Html 형태로 보여주거나 저장 합니다.
그래서.Show() 명령을 내렸을 때 임시 공간에 Html 을 저장한 후 브라우저를 띄워서 보여주게 됩니다.
그래프 작성이 너무 쉽고 가벼워서 .NET 에서 사용하고 싶지만 웹 기반이라 쉽게 GUI 에 넣기가 애매한 상황입니다.

하지만 Webview2 에 넣어서 보여주고, 그 외 몇 가지 자잘한 트릭을 쓰면 GUI 에 Embed 된 것 처럼 사용 가능 합니다.

작성한 Html 을 WebView2 를 사용해 보여주는 방식이기 때문에, C# 이던 WPF 던 관계 없이 정상 작동 합니다.

여기서는 오래간만에 C# WinForm 을 사용해 구현 해 봤습니다.

0. 패키지

Plotly.NET 과 WebView2 를 사용합니다. Visual Studio 없이 단독 실행을 위해서는 WebView2 Runtime 도 설치 해 주셔야 합니다.
Plotly.NET.CSharp 는 Plotly.NET 을 보다 C# 스타일로 사용 가능하게 해 주는 Wrapper 입니다.
그리고 Plotly.NET 는 원래 F# 에서 주로 사용하긴 합니다.
하지만, Plotly.NET 만으로도 충분히 C# 에서 사용 가능 하기 때문에 여기서는 Plotly.NET 만 설치하고 진행 합니다.

1. GUI

그냥 SplitContainer 를 하나 추가해 준게 전부 입니다.

2. frmMain.Designer.cs

#region Windows Form Designer generated code

/// <summary>
///  Required method for Designer support - do not modify
///  the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
	splitContainer1 = new SplitContainer();
	((System.ComponentModel.ISupportInitialize)splitContainer1).BeginInit();
	splitContainer1.SuspendLayout();
	SuspendLayout();
	// 
	// splitContainer1
	// 
	splitContainer1.Dock = DockStyle.Fill;
	splitContainer1.Location = new Point(0, 0);
	splitContainer1.Name = "splitContainer1";
	splitContainer1.Size = new Size(800, 450);
	splitContainer1.SplitterDistance = 400;
	splitContainer1.TabIndex = 0;
	// 
	// frmMain
	// 
	AutoScaleDimensions = new SizeF(7F, 15F);
	AutoScaleMode = AutoScaleMode.Font;
	ClientSize = new Size(800, 450);
	Controls.Add(splitContainer1);
	Name = "frmMain";
	Text = "Main";
	((System.ComponentModel.ISupportInitialize)splitContainer1).EndInit();
	splitContainer1.ResumeLayout(false);
	ResumeLayout(false);
}

#endregion

private SplitContainer splitContainer1;

보통 디자이너 파일을 직접 수정할 일은 없긴 하지만 확인을 위해 코드를 적어 둡니다.

3. 초기화

Random random = new Random(Guid.NewGuid().GetHashCode() + DateTime.Now.Millisecond);

WebView2 webView0 = new WebView2();
WebView2 webView1 = new WebView2();

public frmMain()
{
	InitializeComponent();

	Directory.GetFiles(Environment.CurrentDirectory, "*.html")
		.ToList()
		.ForEach(file => File.Delete(file));

	splitContainer1.Panel1.Controls.Add(webView0);
	splitContainer1.Panel2.Controls.Add(webView1);

	this.Load += FrmMain_Load;
	this.Resize += FrmMain_Resize;
}

WebView2 를 두 개 만들어 붙입니다.
Random 은 난수 데이터 생성을 위해 만들었습니다.
*.html 을 지우는 부분이 있는데, Plotly.NET 에서 스스로 그래프의 크기를 조절해주지 않아서 Main 창의 크기가 바뀔 때 마다 html 을 생성해서 바꿔치기 하는 방식으로 구현 했습니다.
때문에 html 파일이 쌓여서 시작할 때 마다 html 파일을 지워주는 부분 입니다.

사실 이 부분은 논란이 있기는 한데, 원래 크기를 Auto 로 하고 크기가 바뀔 때 마다 적용해주는 설정이 존재 합니다.
하지만 생성된 Html 을 열어보면 분명히 설정이 들어가 있음에도 의도한 대로 동작하지 않아 이런 식으로 구현 했습니다.

4. 그래프 그리기 함수 실행

private void FrmMain_Load(object? sender, EventArgs e)
{
	DrawPlot();
}

private void FrmMain_Resize(object? sender, EventArgs e)
{
	DrawPlot();
}

private void DrawPlot()
{
	splitContainer1.SplitterDistance = splitContainer1.Width / 2;
	ExampleScatterChart();
	ExampleConeChart();
}

splitContainer 의 왼쪽 오른쪽 패널의 크기를 동일하게 맞춰주는 부분과 그리기 함수를 실행하는 부분으로 이루어져 있습니다.

5. 분산 차트

public void ExampleScatterChart()
{
	List<double> lstX = new List<double>();
	List<double> lstY = new List<double>();
	List<double> lstZ = new List<double>();

	for (int i = 0; i < 30; i++)
	{
		lstX.Add(random.Next(0, 100));
		lstY.Add(random.Next(0, 100));
		lstZ.Add(random.Next(0, 100));
	}

	var margin = Plotly.NET.LayoutObjects.Margin.init<int, int, int, int, int, bool>(
		0, 0, 0, 0, 0, true);

	var layout = Plotly.NET.Layout.init<string>(
			Margin: margin
		);

	int width = splitContainer1.SplitterDistance;
	int height = splitContainer1.Height;

	int plotlyScatterWidth = width - 16;
	int plotlyScatterHeight = height - 16;

	webView0.Width = width;
	webView0.Height = height;

	var chart = Chart3D.Chart.Scatter3D<double, double, double, string>(
		x: lstX.ToArray(), y: lstY.ToArray(), z: lstZ.ToArray(),
		mode: StyleParam.Mode.Markers,
		Name: "3D Scatter").WithMarkerStyle(
		Color: Plotly.NET.Color.fromARGB(128, 255, 0, 255),
		Size: 6)
		.WithLayout(layout).WithSize(plotlyScatterWidth, plotlyScatterHeight);

	string fileName = Guid.NewGuid().ToString() + ".html";

	chart.SaveHtml(fileName);

	webView0.Source = new Uri(Path.Combine(Environment.CurrentDirectory, fileName));
}

14: 상하좌우의 마진을 없애고 그래프가 잘 표시가 안될경우 강제로 마진을 조절하는 플래그를 설정 합니다.
24, 25: 여백을 제외한 실제 그래프의 크기를 결정 합니다.
30: WithLayout 에서 마진을, WithSize 에서 그래프의 크기를 설정하여 그래프를 그립니다.
40: 그래프를 Html 로 저장합니다.
42: webView0 에 html 파일을 적용 합니다.

6. Cone 차트

private void ExampleConeChart()
{
	List<(double, double, double, double, double, double)> data = new List<(double, double, double, double, double, double)>();

	int x, y, z = 0;
	int u, v, w = 0;

	for (int i = 0; i < 30; i++)
	{
		x = random.Next(0, 100);
		y = random.Next(0, 100);
		z = random.Next(0, 100);
		u = random.Next(0, 10);
		v = random.Next(0, 10);
		w = random.Next(0, 10);

		data.Add((x, y, z, u, v, w));
	}

	IEnumerable<System.Tuple<double, double, double, double, double, double>> enumerableData =
		data.Select(d => new System.Tuple<double, double, double, double, double, double>(
			d.Item1, d.Item2, d.Item3, d.Item4, d.Item5, d.Item6));

	var margin = Plotly.NET.LayoutObjects.Margin.init<int, int, int, int, int, bool>(
		0, 0, 0, 0, 0, true);


	var layout = Plotly.NET.Layout.init<string>(
			Margin: margin
		);

	int width = splitContainer1.Width - splitContainer1.SplitterDistance;
	int height = splitContainer1.Height;

	int plotlyScatterWidth = width - 16;
	int plotlyScatterHeight = height - 16;

	webView1.Width = width;
	webView1.Height = height;

	GenericChart chart = Chart3D.Chart.Cone<double, double, double, double, double, double, string>(enumerableData, Text: "Cone Chart")
		.WithLayout(layout).WithSize(plotlyScatterWidth, plotlyScatterHeight);

	string fileName = Guid.NewGuid().ToString() + ".html";

	chart.SaveHtml(fileName);

	webView1.Source = new Uri(Path.Combine(Environment.CurrentDirectory, fileName));
}

위와 동일한 내용 입니다. 설명은 생략 합니다.

7. 실행 결과

FrmMain_Resize 이벤트에 의해 창의 크기가 달라질 때 마다 그래프가 그려진 Html 파일을 생성하고 적용 합니다.

8. 크기 자동 조절 설정

var layout = Layout.init<string>(
		Margin: margin,
		AutoSize: true
	);

var config = Config.init(
	Responsive: true
);

//.WithLayout(layout).WithSize(plotlyScatterWidth, plotlyScatterHeight);
.WithLayout(layout).WithConfig(config);

사실, 매뉴얼에 따르면 다음과 같이 설정 하였을 경우 크기가 자동으로 조절이 된다고 합니다.
직접 테스트 해 본 결과, Html 파일에 위 내용이 들어가 있음을 확인 했었음에도 크기가 자동으로 조절되지 않았습니다.
혹시 이 부분에 대해 아시는 게 있으시거나, 자동 조절을 성공하신 분은 알려주시면 감사하겠습니다.

궁금 하신 점이 있으시면 언제든 문의 주시면 답변해 드리겠습니다.

댓글 달기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

위로 스크롤