728x90
반응형
728x170
▶ Wedge.cs
using System.Drawing;
using System.Drawing.Drawing2D;
namespace TestProject
{
/// <summary>
/// 웻지
/// </summary>
public class Wedge
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Field
////////////////////////////////////////////////////////////////////////////////////////// Public
#region Field
/// <summary>
/// 그래픽스 패스
/// </summary>
public GraphicsPath Path;
/// <summary>
/// 전경색
/// </summary>
public Color ForegroundColor;
/// <summary>
/// 배경색
/// </summary>
public Color BackgroundColor;
/// <summary>
/// 텍스트
/// </summary>
public string Text;
/// <summary>
/// 숨김 여부
/// </summary>
public bool IsHidden;
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 생성자 - Wedge(path, foregroundColor, backgroundColor, text, isHidden)
/// <summary>
/// 생성자
/// </summary>
/// <param name="path">그래픽스 패스</param>
/// <param name="foregroundColor">전경색</param>
/// <param name="backgroundColor">배경색</param>
/// <param name="text">텍스트</param>
/// <param name="isHidden">숨김 여부</param>
public Wedge(GraphicsPath path, Color foregroundColor, Color backgroundColor, string text, bool isHidden)
{
Path = path;
ForegroundColor = foregroundColor;
BackgroundColor = backgroundColor;
Text = text;
IsHidden = isHidden;
}
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Method
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 포인트 포함 여부 구하기 - ContainsPoint(point)
/// <summary>
/// 포인트 포함 여부 구하기
/// </summary>
/// <param name="point">포인트</param>
/// <returns>포인트 포함 여부</returns>
public bool ContainsPoint(Point point)
{
return Path.IsVisible(point);
}
#endregion
#region 문자열 구하기 - ToString()
/// <summary>
/// 문자열 구하기
/// </summary>
/// <returns>문자열</returns>
public override string ToString()
{
return Text;
}
#endregion
}
}
728x90
▶ MainForm.cs
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Text;
using System.Linq;
using System.Windows.Forms;
using System.Xml;
namespace TestProject
{
/// <summary>
/// 메인 폼
/// </summary>
public partial class MainForm : Form
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Field
////////////////////////////////////////////////////////////////////////////////////////// Private
#region Field
/// <summary>
/// XML 문서
/// </summary>
private XmlDocument xmlDocument = new XmlDocument();
/// <summary>
/// 웻지 리스트
/// </summary>
private List<Wedge> wedgeList = null;
/// <summary>
/// 마우스 아래 웻지
/// </summary>
private Wedge wedgeUnderMouse = null;
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 생성자 - MainForm()
/// <summary>
/// 생성자
/// </summary>
public MainForm()
{
InitializeComponent();
Load += Form_Load;
Resize += Form_Resize;
this.pictureBox.MouseMove += pictureBox_MouseMove;
}
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Method
////////////////////////////////////////////////////////////////////////////////////////// Private
//////////////////////////////////////////////////////////////////////////////// Event
#region 폼 로드시 처리하기 - Form_Load(sender, e)
/// <summary>
/// 폼 로드시 처리하기
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
private void Form_Load(object sender, EventArgs e)
{
this.xmlDocument.Load("test.xml");
LoadTreeViewData(this.xmlDocument, treeView);
this.treeView.ExpandAll();
SetSunburstChart();
}
#endregion
#region 폼 크기 변경시 처리하기 - Form_Resize(sender, e)
/// <summary>
/// 폼 크기 변경시 처리하기
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
private void Form_Resize(object sender, EventArgs e)
{
SetSunburstChart();
}
#endregion
#region 픽처 박스 마우스 이동시 처리하기 - pictureBox_MouseMove(sender, e)
/// <summary>
/// 픽처 박스 마우스 이동시 처리하기
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
private void pictureBox_MouseMove(object sender, MouseEventArgs e)
{
foreach(Wedge wedge in this.wedgeList)
{
if(wedge.ContainsPoint(e.Location))
{
DisplayWedgeInformation(wedge);
return;
}
}
DisplayWedgeInformation(null);
}
#endregion
//////////////////////////////////////////////////////////////////////////////// Function
#region 트리 뷰 노드 추가하기 - AddTreeViewNode(parentNodeCollection, node)
/// <summary>
/// 트리 뷰 노드 추가하기
/// </summary>
/// <param name="parentNodeCollection">부모 노드 컬렉션</param>
/// <param name="node">노드</param>
private void AddTreeViewNode(TreeNodeCollection parentNodeCollection, XmlNode node)
{
TreeNode newNode = parentNodeCollection.Add(node.Name);
foreach(XmlNode childNode in node.ChildNodes)
{
AddTreeViewNode(newNode.Nodes, childNode);
}
}
#endregion
#region 트리 뷰 데이터 로드하기 - LoadTreeViewData(document, treeView)
/// <summary>
/// 트리 뷰 데이터 로드하기
/// </summary>
/// <param name="document">XML 문서</param>
/// <param name="treeView">트리 뷰</param>
private void LoadTreeViewData(XmlDocument document, TreeView treeView)
{
treeView.Nodes.Clear();
AddTreeViewNode(treeView.Nodes, document.DocumentElement);
}
#endregion
#region 깊이 구하기 - FindDepth(node)
/// <summary>
/// 깊이 구하기
/// </summary>
/// <param name="node">노드</param>
/// <returns>깊이</returns>
private int FindDepth(XmlNode node)
{
int depth = 1;
foreach(XmlNode childNode in node.ChildNodes)
{
int childDepth = FindDepth(childNode);
if(depth < 1 + childDepth)
{
depth = 1 + childDepth;
}
}
return depth;
}
#endregion
#region 노드 색상 구하기 - GetNodeColor(node, colorName, defaultColor)
/// <summary>
/// 노드 색상 구하기
/// </summary>
/// <param name="node">노드</param>
/// <param name="colorName">색상명</param>
/// <param name="defaultColor">디폴트 색상</param>
/// <returns>노드 색상</returns>
private Color GetNodeColor(XmlNode node, string colorName, Color defaultColor)
{
if(node.Attributes[colorName] == null)
{
return defaultColor;
}
try
{
return Color.FromName(node.Attributes[colorName].Value);
}
catch
{
return defaultColor;
}
}
#endregion
#region 숨김 여부 구하기 - IsHidden(node)
/// <summary>
/// 숨김 여부 구하기
/// </summary>
/// <param name="node">노드</param>
/// <returns>숨김 여부</returns>
private bool IsHidden(XmlNode node)
{
if(node.Attributes["IsHidden"] == null)
{
return false;
}
return (bool.Parse(node.Attributes["IsHidden"].Value));
}
#endregion
#region 중앙 정렬 텍스트 그리기 - DrawCenteredText(graphics, font, brush, text, centerPoint, angle)
/// <summary>
/// 중앙 정렬 텍스트 그리기
/// </summary>
/// <param name="graphics">그래픽스</param>
/// <param name="font">폰트</param>
/// <param name="brush">브러시</param>
/// <param name="text">텍스트</param>
/// <param name="centerPoint">중심 포인트</param>
/// <param name="angle">각도</param>
private void DrawCenteredText(Graphics graphics, Font font, Brush brush, string text, PointF centerPoint, float angle)
{
graphics.RotateTransform(angle);
graphics.TranslateTransform(centerPoint.X, centerPoint.Y, MatrixOrder.Append);
using(StringFormat stringFormat = new StringFormat())
{
stringFormat.LineAlignment = StringAlignment.Center;
graphics.DrawString(text, font, brush, 0, 0, stringFormat);
}
graphics.ResetTransform();
}
#endregion
#region 호 위에 텍스트 그리기 - DrawTextOnArc(graphics, brush, font, text, centerX, centerY,
minimumTheta, maximumTheta, radius)
/// <summary>
/// 호 위에 텍스트 그리기
/// </summary>
/// <param name="graphics">그래픽스</param>
/// <param name="brush">브러시</param>
/// <param name="font">폰트</param>
/// <param name="text">텍스트</param>
/// <param name="centerX">중심 X</param>
/// <param name="centerY">중심 Y</param>
/// <param name="minimumTheta">최소 세타</param>
/// <param name="maximumTheta">최대 세타</param>
/// <param name="radius">반경</param>
private void DrawTextOnArc
(
Graphics graphics,
Brush brush,
Font font,
string text,
float centerX,
float centerY,
double minimumTheta,
double maximumTheta,
float radius
)
{
if(text.Length > 32)
{
text = text.Substring(0, 32);
}
List<CharacterRange> characterRangeList = new List<CharacterRange>();
for(int i = 0; i < text.Length; i++)
{
characterRangeList.Add(new CharacterRange(i, 1));
}
using(StringFormat stringFormat = new StringFormat())
{
stringFormat.SetMeasurableCharacterRanges(characterRangeList.ToArray());
RectangleF rectangle = new RectangleF(0, 0, 1000, 1000);
Region[] regionArray = graphics.MeasureCharacterRanges(text, font, rectangle, stringFormat);
float totalWidth = regionArray.Sum(region => region.GetBounds(graphics).Width);
double totalTheta = totalWidth / radius;
double startTheta = (minimumTheta + maximumTheta - totalTheta) / 2;
double thetaPerWidth = totalTheta / totalWidth;
for(int i = 0; i < regionArray.Length; i++)
{
float x = (float)(centerX + radius * Math.Cos(startTheta));
float y = (float)(centerY + radius * Math.Sin(startTheta));
float angle = (float)(startTheta / Math.PI * 180 + 90);
PointF point = new PointF(x, y);
DrawCenteredText
(
graphics,
font,
brush,
text[i].ToString(),
point,
angle
);
startTheta += thetaPerWidth * regionArray[i].GetBounds(graphics).Width;
}
}
}
#endregion
#region 웻지에서 노드 텍스트 그리기 - DrawNodeTextInWedge(graphics, brush, font, text, centerX, centerY,
minimumTheta, maximumTheta, middleRadius)
/// <summary>
/// 웻지에서 노드 텍스트 그리기
/// </summary>
/// <param name="graphics">그래픽스</param>
/// <param name="brush">브러시</param>
/// <param name="font">폰트</param>
/// <param name="text">텍스트</param>
/// <param name="centerX">중심 X</param>
/// <param name="centerY">중심 Y</param>
/// <param name="minimumTheta">최소 세타</param>
/// <param name="maximumTheta">최대 세타</param>
/// <param name="middleRadius">중간 반경</param>
private void DrawNodeTextInWedge
(
Graphics graphics,
Brush brush,
Font font,
string text,
float centerX,
float centerY,
double minimumTheta,
double maximumTheta,
float middleRadius
)
{
string[] separatorArray = { @"\n" };
string[] lineArray = text.Split(separatorArray, StringSplitOptions.None);
float height = font.Height * 0.8f;
float minimumRadius = middleRadius - (lineArray.Length - 1) * height / 2f;
float maximumRadius = minimumRadius + (lineArray.Length - 1) * height;
float deltaRadius = (maximumRadius - minimumRadius) / (float)(lineArray.Length - 1);
float radius = maximumRadius;
for(int i = 0; i < lineArray.Length; i++)
{
DrawTextOnArc
(
graphics,
brush,
font,
lineArray[i],
centerX,
centerY,
minimumTheta,
maximumTheta,
radius
);
radius -= deltaRadius;
}
}
#endregion
#region 햇살 자식 그리기 - DrawSunburstChild(graphics, centerX, centerY, deltaRoot, level, node,
minimumAngle, maximumAngle, font, defaultBackgroundColor, defaultForegroundColor, backgroundColor,
foregroundColor, wedgeList)
/// <summary>
/// 햇살 자식 그리기
/// </summary>
/// <param name="graphics">그래픽스</param>
/// <param name="centerX">중심 X</param>
/// <param name="centerY">중심 Y</param>
/// <param name="deltaRoot">델타 루트</param>
/// <param name="level">레벨</param>
/// <param name="node">노드</param>
/// <param name="minimumAngle">최소 각도</param>
/// <param name="maximumAngle">최대 각도</param>
/// <param name="font">폰트</param>
/// <param name="defaultBackgroundColor">디폴트 배경색</param>
/// <param name="defaultForegroundColor">디폴트 전경색</param>
/// <param name="backgroundColor">배경색</param>
/// <param name="foregroundColor">전경색</param>
/// <param name="wedgeList">웻지 리스트</param>
private void DrawSunburstChild
(
Graphics graphics,
float centerX,
float centerY,
float deltaRoot,
int level,
XmlNode node,
float minimumAngle,
float maximumAngle,
Font font,
Color defaultBackgroundColor,
Color defaultForegroundColor,
out Color backgroundColor,
out Color foregroundColor,
ref List<Wedge> wedgeList
)
{
double minimumTheta = minimumAngle / 180f * Math.PI;
double maximumTheta = maximumAngle / 180f * Math.PI;
float innerRadius = level * deltaRoot;
float outerRadius = innerRadius + deltaRoot;
RectangleF outerRectangle = new RectangleF
(
centerX - outerRadius,
centerY - outerRadius,
2 * outerRadius,
2 * outerRadius
);
RectangleF innerRectangle = new RectangleF
(
centerX - innerRadius,
centerY - innerRadius,
2 * innerRadius,
2 * innerRadius
);
float innerMinimumX = (float)(centerX + innerRadius * Math.Cos(minimumTheta));
float innerMinimumY = (float)(centerY + innerRadius * Math.Sin(minimumTheta));
float outerMinimumX = (float)(centerX + outerRadius * Math.Cos(minimumTheta));
float outerMinimumY = (float)(centerY + outerRadius * Math.Sin(minimumTheta));
float innerMaximumX = (float)(centerX + innerRadius * Math.Cos(maximumTheta));
float innerMaximumY = (float)(centerY + innerRadius * Math.Sin(maximumTheta));
float outerMaximumX = (float)(centerX + outerRadius * Math.Cos(maximumTheta));
float outerMaximumY = (float)(centerY + outerRadius * Math.Sin(maximumTheta));
GraphicsPath path = new GraphicsPath();
path.AddArc (outerRectangle, minimumAngle, maximumAngle - minimumAngle);
path.AddLine(outerMaximumX, outerMaximumY, innerMaximumX, innerMaximumY);
path.AddArc (innerRectangle, maximumAngle, minimumAngle - maximumAngle);
path.AddLine(innerMinimumX, innerMinimumY, outerMinimumX, outerMinimumY);
path.CloseFigure();
bool isHidden = IsHidden(node);
backgroundColor = GetNodeColor(node, "BgColor", defaultBackgroundColor);
if(!isHidden)
{
using(Brush brush = new SolidBrush(backgroundColor))
{
graphics.FillPath(brush, path);
}
graphics.DrawPath(Pens.Black, path);
}
string text = node.Name;
if(node.Attributes["Text"] != null)
{
text = node.Attributes["Text"].Value;
}
float middleRadius = (innerRadius + outerRadius) / 2;
foregroundColor = GetNodeColor(node, "FgColor", defaultForegroundColor);
if(!isHidden)
{
using(Brush brush = new SolidBrush(foregroundColor))
{
DrawNodeTextInWedge
(
graphics,
brush,
font,
text,
centerX,
centerY,
minimumTheta,
maximumTheta,
middleRadius
);
}
}
wedgeList.Add(new Wedge(path, foregroundColor, backgroundColor, text, isHidden));
}
#endregion
#region 햇살 자식 그리기 - DrawSunburstChildren(graphics, centerX, centerY, deltaRoot, level, childNodeList,
minimumAngle, maximumAngle, font, parentBackgroundColor, parentForegroundColor, wedgeList)
/// <summary>
/// 햇살 자식 그리기
/// </summary>
/// <param name="graphics">그래픽스</param>
/// <param name="centerX">중심 X</param>
/// <param name="centerY">중심 Y</param>
/// <param name="deltaRoot">델타 루트</param>
/// <param name="level">레벨</param>
/// <param name="childNodeList">자식 노드 리스트</param>
/// <param name="minimumAngle">최소 각도</param>
/// <param name="maximumAngle">최대 각도</param>
/// <param name="font">폰트</param>
/// <param name="parentBackgroundColor">부모 배경색</param>
/// <param name="parentForegroundColor">부모 전경색</param>
/// <param name="wedgeList">웻지 리스트</param>
private void DrawSunburstChildren
(
Graphics graphics,
float centerX,
float centerY,
float deltaRoot,
int level,
XmlNodeList childNodeList,
float minimumAngle,
float maximumAngle,
Font font,
Color parentBackgroundColor,
Color parentForegroundColor,
ref List<Wedge> wedgeList
)
{
int childNodeCount = childNodeList.Count;
float angle = minimumAngle;
float deltaAngle = (maximumAngle - minimumAngle) / childNodeCount;
foreach(XmlNode childNode in childNodeList)
{
Color childBackgroundColor;
Color childForegroundColor;
DrawSunburstChild
(
graphics,
centerX,
centerY,
deltaRoot,
level,
childNode,
angle,
angle + deltaAngle,
font,
parentBackgroundColor,
parentForegroundColor,
out childBackgroundColor,
out childForegroundColor,
ref wedgeList
);
DrawSunburstChildren
(
graphics,
centerX,
centerY,
deltaRoot,
level + 1,
childNode.ChildNodes,
angle,
angle + deltaAngle,
font,
childBackgroundColor,
childForegroundColor,
ref wedgeList
);
angle = angle + deltaAngle;
}
}
#endregion
#region 햇살 차트 이미지 구하기 - GetSunburstChartImage(width, height, margin, xmlDocument,
backgroundColor, arcPen, font, wedgeList)
/// <summary>
/// 햇살 차트 이미지 구하기
/// </summary>
/// <param name="width">너비</param>
/// <param name="height">높이</param>
/// <param name="margin">마진</param>
/// <param name="xmlDocument">XML 문서</param>
/// <param name="backgroundColor">배경색</param>
/// <param name="arcPen">호 펜</param>
/// <param name="font">폰트</param>
/// <param name="wedgeList">웻지 리스트</param>
/// <returns>햇살 차트 이미지</returns>
private Bitmap GetSunburstChartImage
(
int width,
int height,
int margin,
XmlDocument xmlDocument,
Color backgroundColor,
Pen arcPen,
Font font,
ref List<Wedge> wedgeList
)
{
Bitmap bitmap = new Bitmap(width, height);
using(Graphics graphics = Graphics.FromImage(bitmap))
{
graphics.Clear(backgroundColor);
graphics.SmoothingMode = SmoothingMode.AntiAlias;
graphics.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
int depth = FindDepth(xmlDocument.DocumentElement);
float centerX = width / 2f;
float centerY = height / 2f;
width -= 2 * margin;
height -= 2 * margin;
float deltaRoot = (Math.Min(width, height) / 2f / depth);
RectangleF rootRectangle = new RectangleF
(
centerX - deltaRoot,
centerY - deltaRoot,
2 * deltaRoot,
2 * deltaRoot
);
Color backgroundNodeColor = GetNodeColor
(
this.xmlDocument.DocumentElement,
"BgColor",
Color.Transparent
);
using(Brush brush = new SolidBrush(backgroundNodeColor))
{
graphics.FillEllipse(brush, rootRectangle);
}
graphics.DrawEllipse(arcPen, rootRectangle);
Color foregroundNodeColor = GetNodeColor
(
this.xmlDocument.DocumentElement,
"FgColor",
Color.Black
);
using(Brush brush = new SolidBrush(foregroundNodeColor))
{
using(StringFormat stringFormat = new StringFormat())
{
stringFormat.Alignment = StringAlignment.Center;
stringFormat.LineAlignment = StringAlignment.Center;
graphics.DrawString
(
this.xmlDocument.DocumentElement.Name,
font,
brush,
centerX,
centerY,
stringFormat
);
}
}
GraphicsPath path = new GraphicsPath();
path.AddEllipse(rootRectangle);
wedgeList.Add
(
new Wedge
(
path,
foregroundNodeColor,
backgroundNodeColor,
this.xmlDocument.DocumentElement.Name,
IsHidden(this.xmlDocument.DocumentElement)
)
);
DrawSunburstChildren
(
graphics,
centerX,
centerY,
deltaRoot,
1,
this.xmlDocument.DocumentElement.ChildNodes,
0,
360,
font,
backgroundNodeColor,
foregroundNodeColor,
ref wedgeList
);
}
return bitmap;
}
#endregion
#region 햇살 차트 설정하기 - SetSunburstChart()
/// <summary>
/// 햇살 차트 설정하기
/// </summary>
private void SetSunburstChart()
{
this.wedgeList = new List<Wedge>();
using(Font font = new Font("나눔고딕코딩", 12, FontStyle.Bold))
{
this.pictureBox.Image = GetSunburstChartImage
(
this.pictureBox.ClientSize.Width,
this.pictureBox.ClientSize.Height,
4,
this.xmlDocument,
Color.White,
Pens.Blue,
font,
ref this.wedgeList
);
}
}
#endregion
#region 웻지 정보 표시하기 - DisplayWedgeInformation(wedge)
/// <summary>
/// 웻지 정보 표시하기
/// </summary>
/// <param name="wedge">웻지</param>
private void DisplayWedgeInformation(Wedge wedge)
{
if(wedge == this.wedgeUnderMouse)
{
return;
}
this.wedgeUnderMouse = wedge;
if((wedge == null) || (wedge.IsHidden))
{
this.wedgeUnderMouseLabel.Text = string.Empty;
}
else
{
this.wedgeUnderMouseLabel.Text = wedge.Text.Replace("\\n", " ");
}
}
#endregion
}
}
728x90
반응형
그리드형(광고전용)
'C# > WinForm' 카테고리의 다른 글
[C#/WINFORM] 원형 호와 2개의 선 세그먼트 연결하기 (0) | 2020.07.27 |
---|---|
[C#/WINFORM] Graphics 클래스 : DrawEllipse 메소드를 사용해 포인트 채우기 (0) | 2020.07.27 |
[C#/WINFORM] Graphics 클래스 : FillEllipse 메소드를 사용해 포인트 그리기 (0) | 2020.07.27 |
[C#/WINFORM] Point 구조체 : 거리 구하기 (0) | 2020.07.27 |
[C#/WINFORM] PictureBox 클래스 : 배경 이미지 위에 그리기 (0) | 2020.07.27 |
[C#/WINFORM] 햇살(Sunburst) 차트에서 마우스 아래 섹션 찾기 (0) | 2020.07.27 |
[C#/WINFORM] ListBox 클래스 : 사용자 항목 드래그하기 (0) | 2020.07.26 |
[C#/WINFORM] ListBox 클래스 : 사용자 항목 배열하기 (0) | 2020.07.26 |
[C#/WINFORM] GraphicsPath 클래스 : 라운드 사각형 패스 구하기 (0) | 2020.07.24 |
[C#/WINFORM] 핀터레스트 스타일 대각선 그림 몽타지 사용하기 (0) | 2020.07.24 |
[C#/WINFORM] Bitmap 클래스 : 색상 영역 설정하기 (0) | 2020.07.23 |
댓글을 달아 주세요