첨부 실행 코드는 나눔고딕코딩 폰트를 사용합니다.

728x90
반응형
728x170

TestProject.zip
0.02MB

▶ GraphicsExtension.cs

using System;
using System.Collections.Generic;
using System.Drawing;

namespace TestProject
{
    /// <summary>
    /// 그래픽스 확장
    /// </summary>
    public static class GraphicsExtension
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// 난수기
        /// </summary>
        private static Random _random = new Random();

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 포인트 그리기 - DrawPoint(graphics, point, brush, pen, radius)

        /// <summary>
        /// 포인트 그리기
        /// </summary>
        /// <param name="graphics">그래픽스</param>
        /// <param name="point">포인트</param>
        /// <param name="brush">브러시</param>
        /// <param name="pen">펜</param>
        /// <param name="radius">반경</param>
        public static void DrawPoint(this Graphics graphics, PointF point, Brush brush, Pen pen, float radius)
        {
            RectangleF rectangle = new RectangleF
            (
                point.X - radius,
                point.Y - radius,
                2 * radius,
                2 * radius
            );

            graphics.FillEllipse(brush, rectangle);

            graphics.DrawEllipse(pen, rectangle);
        }

        #endregion
        #region 포인트 리스트 그리기 - DrawPointList(graphics, pointList, brush, pen, radius)

        /// <summary>
        /// 포인트 리스트 그리기
        /// </summary>
        /// <param name="graphics">그래픽스</param>
        /// <param name="pointList">포인트 리스트</param>
        /// <param name="brush">브러시</param>
        /// <param name="pen">펜</param>
        /// <param name="radius">반경</param>
        public static void DrawPointList(this Graphics graphics, List<PointF> pointList, Brush brush, Pen pen, float radius)
        {
            if(pointList == null)
            {
                return;
            }

            foreach(PointF point in pointList)
            {
                graphics.DrawPoint(point, brush, pen, radius);
            }
        }

        #endregion
        #region 사각형 그리기 - DrawRectangle(graphics, pen, rectangle)

        /// <summary>
        /// 사각형 그리기
        /// </summary>
        /// <param name="graphics">그래픽스</param>
        /// <param name="pen">펜</param>
        /// <param name="rectangle">사각형</param>
        public static void DrawRectangle(this Graphics graphics, Pen pen, RectangleF rectangle)
        {
            graphics.DrawRectangle(pen, rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height);
        }

        #endregion
        #region 사각형 그리기 - DrawRectangle(graphics, point, brush, pen, radius)

        /// <summary>
        /// 사각형 그리기
        /// </summary>
        /// <param name="graphics">그래픽스</param>
        /// <param name="point">포인트</param>
        /// <param name="brush">브로시</param>
        /// <param name="pen">펜</param>
        /// <param name="radius">반경</param>
        public static void DrawRectangle(this Graphics graphics, PointF point, Brush brush, Pen pen, float radius)
        {
            RectangleF rectangle = new RectangleF
            (
                point.X - radius,
                point.Y - radius,
                2 * radius,
                2 * radius
            );

            graphics.FillRectangle(brush, rectangle);

            graphics.DrawRectangle(pen, rectangle);
        }

        #endregion
        #region 십자가 그리기 - DrawCross(graphics, pen, point, radius)

        /// <summary>
        /// 십자가 그리기
        /// </summary>
        /// <param name="graphics">그래픽스</param>
        /// <param name="pen">펜</param>
        /// <param name="point">포인트</param>
        /// <param name="radius">반경</param>
        public static void DrawCross(this Graphics graphics, Pen pen, PointF point, float radius)
        {
            graphics.DrawLine(pen, point.X - radius, point.Y, point.X + radius, point.Y);

            graphics.DrawLine(pen, point.X, point.Y - radius, point.X, point.Y + radius);
        }

        #endregion
        #region 십자가 그리기 - DrawCross(graphics, outerColor, innerColor, point, radius)

        /// <summary>
        /// 십자가 그리기
        /// </summary>
        /// <param name="graphics">그래픽스</param>
        /// <param name="outerColor">외부 색상</param>
        /// <param name="innerColor">내부 색상</param>
        /// <param name="point">포인트</param>
        /// <param name="radius">반경</param>
        public static void DrawCross(this Graphics graphics, Color outerColor, Color innerColor, PointF point, float radius)
        {
            using(Pen pen = new Pen(outerColor, 3))
            {
                graphics.DrawLine(pen, point.X - radius - 1, point.Y, point.X + radius + 1, point.Y);

                graphics.DrawLine(pen, point.X, point.Y - radius - 1, point.X, point.Y + radius + 1);
            }

            using(Pen pen = new Pen(innerColor, 1))
            {
                graphics.DrawLine(pen, point.X - radius, point.Y, point.X + radius, point.Y);

                graphics.DrawLine(pen, point.X, point.Y - radius, point.X, point.Y + radius);
            }
        }

        #endregion

        #region 난수 구하기 - Random<T>(list)

        /// <summary>
        /// 난수 구하기
        /// </summary>
        /// <typeparam name="TItem">항목 타입</typeparam>
        /// <param name="list">리스트</param>
        /// <returns>난수 값</returns>
        public static TItem Random<TItem>(List<TItem> list)
        {
            return list[_random.Next(list.Count)];
        }

        #endregion
        #region 임의로 섞기 - Randomize<T>(array)

        /// <summary>
        /// 임의로 섞기
        /// </summary>
        /// <typeparam name="TItem">항목 타입</typeparam>
        /// <param name="array">배열</param>
        public static void Randomize<TItem>(this TItem[] array)
        {
            for(int i = 0; i < array.Length - 1; i++)
            {
                int j = _random.Next(i, array.Length);

                TItem temporaryValue = array[i];

                array[i] = array[j];
                array[j] = temporaryValue;
            }
        }

        #endregion
        #region 임의로 섞기 - Randomize<T>(list)

        /// <summary>
        /// 임의로 섞기
        /// </summary>
        /// <typeparam name="TItem">항목 타입</typeparam>
        /// <param name="list">리스트</param>
        public static void Randomize<TItem>(this List<TItem> list)
        {
            TItem[] array = list.ToArray();

            array.Randomize();

            list.Clear();

            list.AddRange(array);
        }

        #endregion
    }
}

 

728x90

 

▶ PointData.cs

using System.Drawing;

namespace TestProject
{
    /// <summary>
    /// 포인트 데이터
    /// </summary>
    public class PointData
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region Field

        /// <summary>
        /// 위치
        /// </summary>
        public PointF Location;

        /// <summary>
        /// 클러스터 번호
        /// </summary>
        public int ClusterNumber;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - PointData(location, clusterNumber)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="location">위치</param>
        /// <param name="clusterNumber">클러스터 번호</param>
        public PointData(PointF location, int clusterNumber)
        {
            Location      = location;
            ClusterNumber = clusterNumber;
        }

        #endregion
        #region 생성자 - PointData(x, y, clusterNumber)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="x">X</param>
        /// <param name="y">Y</param>
        /// <param name="clusterNumber">클러스터 번호</param>
        public PointData(float x, float y, int clusterNumber) : this(new PointF(x, y), clusterNumber)
        {
        }

        #endregion
    }
}

 

300x250

 

▶ MainForm.cs

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;

namespace TestProject
{
    /// <summary>
    /// 메인 폼
    /// </summary>
    public partial class MainForm : Form
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// 포인트 펜 배열
        /// </summary>
        private Pen[] pointPenArray =
        {
            Pens.Red,
            Pens.Green,
            Pens.Blue,
            Pens.Black,
            Pens.Red,
            Pens.Green,
            Pens.Blue,
            Pens.Black
        };

        /// <summary>
        /// 포인트 브러시 배열
        /// </summary>
        private Brush[] pointBrushArray =
        {
            Brushes.Pink,
            Brushes.LightGreen,
            Brushes.LightBlue,
            Brushes.Yellow,
            Brushes.Orange,
            Brushes.Lime,
            Brushes.Cyan,
            Brushes.White
        };

        /// <summary>
        /// 중심점 브러시 배열
        /// </summary>
        private Brush[] centroidBrushArray =
        {
            Brushes.Red,
            Brushes.Green,
            Brushes.Blue,
            Brushes.Yellow,
            Brushes.Orange,
            Brushes.Lime,
            Brushes.Cyan,
            Brushes.White
        };

        /// <summary>
        /// 시드 포인트 리스트
        /// </summary>
        private List<PointF> seedPointList = new List<PointF>();

        /// <summary>
        /// 중심점 리스트
        /// </summary>
        private List<PointF> centroidList = new List<PointF>();

        /// <summary>
        /// 포인트 데이터 리스트
        /// </summary>
        private List<PointData> pointDataList = new List<PointData>();

        /// <summary>
        /// 단계 카운트
        /// </summary>
        private int stepCount = 0;

        /// <summary>
        /// 최대 클러스터 카운트
        /// </summary>
        private int maximumClusterCount = 1;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - MainForm()

        /// <summary>
        /// 생성자
        /// </summary>
        public MainForm()
        {
            InitializeComponent();

            Load                           += Form_Load;
            this.createPointButton.Click   += createPointButton_Click;
            this.createClusterButton.Click += createClusterButton_Click;
            this.clearButton.Click         += clearButton_Click;
            this.pictureBox.MouseClick     += pictureBox_MouseClick;
            this.pictureBox.Paint          += pictureBox_Paint;
            this.fpsScrollBar.Scroll       += fpsScrollBar_Scroll;
            this.timer.Tick                += timer_Tick;
        }

        #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.maximumClusterCount = this.pointBrushArray.Length;
        }

        #endregion
        #region 포인트 생성 버튼 클릭시 처리하기 - createPointButton_Click(sender, e)

        /// <summary>
        /// 포인트 생성 버튼 클릭시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void createPointButton_Click(object sender, EventArgs e)
        {
            AddPointData();
        }

        #endregion
        #region 클러스터 생성 버튼 클릭시 처리하기 - createClusterButton_Click(sender, e)

        /// <summary>
        /// 클러스터 생성 버튼 클릭시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void createClusterButton_Click(object sender, EventArgs e)
        {
            int clusterCount = int.Parse(this.clusterCountTextBox.Text);

            if(this.pointDataList.Count < clusterCount)
            {
                return;
            }

            this.centroidList = new List<PointF>();

            this.pointDataList.Randomize();

            for(int i = 0; i < clusterCount; i++)
            {
                this.centroidList.Add(this.pointDataList[i].Location);
            }

            foreach(PointData pointData in this.pointDataList)
            {
                pointData.ClusterNumber = 0;
            }

            this.stepCount = 0;

            this.pictureBox.Refresh();

            this.scoreLabel.Text = string.Empty;

            Cursor = Cursors.WaitCursor;

            this.timer.Enabled = true;
        }

        #endregion
        #region 지우기 버튼 클릭시 처리하기 - clearButton_Click(sender, e)

        /// <summary>
        /// 지우기 버튼 클릭시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void clearButton_Click(object sender, EventArgs e)
        {
            this.seedPointList.Clear();

            this.centroidList.Clear();

            this.pointDataList.Clear();

            this.pictureBox.Refresh();
        }

        #endregion
        #region 픽처 박스 마우스 클릭시 처리하기 - pictureBox_MouseClick(sender, e)

        /// <summary>
        /// 픽처 박스 마우스 클릭시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void pictureBox_MouseClick(object sender, MouseEventArgs e)
        {
            if(e.Button == MouseButtons.Left)
            {
                this.seedPointList.Add(e.Location);
            }
            else
            {
                this.pointDataList.Add(new PointData(e.Location, 0));
            }

            this.centroidList.Clear();

            this.pictureBox.Refresh();
        }

        #endregion
        #region 픽처 박스 페인트시 처리하기 - pictureBox_Paint(sender, e)

        /// <summary>
        /// 픽처 박스 페인트시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void pictureBox_Paint(object sender, PaintEventArgs e)
        {
            const float RADIUS = 4;

            e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;

            foreach(PointData pointData in this.pointDataList)
            {
                e.Graphics.DrawPoint
                (
                    pointData.Location,
                    this.pointBrushArray[pointData.ClusterNumber % this.maximumClusterCount],
                    this.pointPenArray[pointData.ClusterNumber % this.maximumClusterCount],
                    RADIUS
                );
            }

            for(int i = 0; i < this.centroidList.Count; i++)
            {
                e.Graphics.DrawRectangle
                (
                    this.centroidList[i],
                    this.centroidBrushArray[i % this.maximumClusterCount],
                    Pens.Black,
                    RADIUS
                );
            }

            for(int i = 0; i < this.seedPointList.Count; i++)
            {
                e.Graphics.DrawCross
                (
                    Color.Black,
                    Color.White,
                    this.seedPointList[i],
                    2 * RADIUS
                );
            }
        }

        #endregion
        #region FPS 스크롤바 스크롤시 처리하기 - fpsScrollBar_Scroll(sender, e)

        /// <summary>
        /// FPS 스크롤바 스크롤시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void fpsScrollBar_Scroll(object sender, ScrollEventArgs e)
        {
            int fps = this.fpsScrollBar.Value / 10;

            if(fps < 1)
            {
                fps = 1;
            }

            this.fpsValueLabel.Text = fps.ToString();

            this.timer.Interval = 1000 / fps;
        }

        #endregion
        #region 타이머 틱 처리하기 - timer_Tick(sender, e)

        /// <summary>
        /// 타이머 틱 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void timer_Tick(object sender, EventArgs e)
        {
            this.stepCount++;

            UpdateSolution();

            this.pictureBox.Refresh();
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////// Function

        #region 포인트 데이터 추가하기 - AddPointData()

        /// <summary>
        /// 포인트 데이터 추가하기
        /// </summary>
        private void AddPointData()
        {
            if(this.seedPointList.Count < 1)
            {
                MessageBox.Show("먼저 최소한 하나의 시드를 정의해 주시기 바랍니다.");

                return;
            }

            this.centroidList.Clear();

            Random random = new Random();

            double maximumRadius = Math.Min(this.pictureBox.ClientSize.Width, this.pictureBox.ClientSize.Height) / 6;

            int pointCount = int.Parse(this.pointCountTextBox.Text);

            for(int i = 0; i < pointCount; i++)
            {
                int seedNumber = random.Next(this.seedPointList.Count);

                double radius = maximumRadius * random.NextDouble() + maximumRadius * random.NextDouble();
                double theta  = random.Next(360);

                float x = this.seedPointList[seedNumber].X + (float)(radius * Math.Cos(theta));
                float y = this.seedPointList[seedNumber].Y + (float)(radius * Math.Sin(theta));

                this.pointDataList.Add(new PointData(x, y, 0));
            }

            this.pictureBox.Refresh();
        }

        #endregion
        #region 거리 구하기 - GetDistance(point1, point2)

        /// <summary>
        /// 거리 구하기
        /// </summary>
        /// <param name="point1">포인트 1</param>
        /// <param name="point2">포인트 2</param>
        /// <returns>거리</returns>
        private double GetDistance(PointF point1, PointF point2)
        {
            float deltaX = point1.X - point2.X;
            float deltaY = point1.Y - point2.Y;

            return Math.Sqrt(deltaX * deltaX + deltaY * deltaY);
        }

        #endregion
        #region 점수 구하기 - GetScore()

        /// <summary>
        /// 점수 구하기
        /// </summary>
        /// <returns>점수</returns>
        private int GetScore()
        {
            float score = 0;

            foreach(PointData pointData in this.pointDataList)
            {
                float deltaX = this.centroidList[pointData.ClusterNumber].X - pointData.Location.X;
                float deltaY = this.centroidList[pointData.ClusterNumber].Y - pointData.Location.Y;

                score += deltaX * deltaX + deltaY * deltaY;
            }

            return (int)score;
        }

        #endregion
        #region 솔루션 업데이트하기 - UpdateSolution()

        /// <summary>
        /// 솔루션 업데이트하기
        /// </summary>
        private void UpdateSolution()
        {
            int clusterCount = this.centroidList.Count;

            PointF[] newCenterPointArray = new PointF[clusterCount];

            int[] pointNumberArray = new int[clusterCount];

            foreach(PointData pointData in this.pointDataList)
            {
                double bestDistance = GetDistance(pointData.Location, this.centroidList[0]);

                int bestCluster = 0;

                for(int i = 1; i < clusterCount; i++)
                {
                    double testDistance = GetDistance(pointData.Location, this.centroidList[i]);

                    if(testDistance < bestDistance)
                    {
                        bestDistance = testDistance;

                        bestCluster = i;
                    }
                }

                pointData.ClusterNumber = bestCluster;

                newCenterPointArray[bestCluster].X += pointData.Location.X;
                newCenterPointArray[bestCluster].Y += pointData.Location.Y;

                pointNumberArray[bestCluster]++;
            }

            List<PointF> newCentroidList = new List<PointF>();

            for(int i = 0; i < clusterCount; i++)
            {
                newCentroidList.Add
                (
                    new PointF
                    (
                        newCenterPointArray[i].X / pointNumberArray[i],
                        newCenterPointArray[i].Y / pointNumberArray[i]
                    )
                );
            }

            bool centroidChanged = false;

            for(int i = 0; i < clusterCount; i++)
            {
                const float minimumChange = 0.5f;

                if
                (
                    (Math.Abs(this.centroidList[i].X - newCentroidList[i].X) > minimumChange) ||
                    (Math.Abs(this.centroidList[i].Y - newCentroidList[i].Y) > minimumChange)
                )
                {
                    centroidChanged = true;

                    break;
                }
            }

            if(!centroidChanged)
            {
                this.timer.Enabled = false;

                this.scoreLabel.Text = $"점수 : {GetScore()}, 단계 수 : {this.stepCount}";

                Cursor = Cursors.Default;

                return;
            }

            this.centroidList = newCentroidList;
        }

        #endregion
    }
}
728x90
반응형
그리드형(광고전용)
Posted by icodebroker

댓글을 달아 주세요