첨부 소스 코드는 나눔고딕코딩 폰트를 사용합니다.
728x90
반응형
728x170

TestProject.zip
다운로드

▶ MathHelper.cs

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

namespace TestProject
{
    /// <summary>
    /// 수학 헬퍼
    /// </summary>
    public class MathHelper
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 제곱 오차 구하기 - GetErrorSquared(pointList, a, b)

        /// <summary>
        /// 제곱 오차 구하기
        /// </summary>
        /// <param name="pointList">포인트 리스트</param>
        /// <param name="a">A</param>
        /// <param name="b">B</param>
        /// <returns>제곱 오차</returns>
        public static double GetErrorSquared(List<PointF> pointList, double a, double b)
        {
            double result = 0;

            foreach(PointF point in pointList)
            {
                double error = point.Y - (a * point.X + b);

                result += error * error;
            }

            return result;
        }

        #endregion
        #region 선형 최소 제곱법 해 구하기 - GetLinearLeastSquaresFit(pointList, a, b)

        /// <summary>
        /// 선형 최소 제곱법 해 구하기
        /// </summary>
        /// <param name="pointList">포인트 리스트</param>
        /// <param name="a">A</param>
        /// <param name="b">B</param>
        /// <returns>오차</returns>
        public static double GetLinearLeastSquaresFit(List<PointF> pointList, out double a, out double b)
        {
            double count     = pointList.Count;
            double xSummary  = 0;
            double ySummary  = 0;
            double xxSummary = 0;
            double xySummary = 0;

            foreach(PointF point in pointList)
            {
                xSummary  += point.X;
                ySummary  += point.Y;
                xxSummary += point.X * point.X;
                xySummary += point.X * point.Y;
            }

            a = (xySummary * count - xSummary * ySummary) / (xxSummary * count - xSummary * xSummary);
            b = (xySummary * xSummary - ySummary * xxSummary) / (xSummary * xSummary - count * xxSummary);

            return Math.Sqrt(GetErrorSquared(pointList, a, b));
        }

        #endregion
    }
}

 

728x90

 

▶ 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>
        /// X 최소값
        /// </summary>
        private const float X_MINIMUM = -10.0f;

        /// <summary>
        /// X 최대값
        /// </summary>
        private const float X_MAXIMUM =  10.0f;

        /// <summary>
        /// Y 최소값
        /// </summary>
        private const float Y_MINIMUM = -10.0f;

        /// <summary>
        /// Y 최대값
        /// </summary>
        private const float Y_MAXIMUM =  10.0f;

        /// <summary>
        /// 그리기 변환
        /// </summary>
        private Matrix drawingTransform;

        /// <summary>
        /// 반전 변환
        /// </summary>
        private Matrix inverseTransform;

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

        /// <summary>
        /// 솔루션 여부
        /// </summary>
        private bool hasSolution = false;

        /// <summary>
        /// 최적 A
        /// </summary>
        private double bestA;
        
        /// <summary>
        /// 최적 B
        /// </summary>
        private double bestB;

        #endregion

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

        #region 생성자 - MainForm()

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

            #region 이벤트를 설정한다.

            Load                            += Form_Load;
            this.calculateButton.Click      += calculateButton_Click;
            this.clearButton.Click          += clearButton_Click;
            this.graphButton.Click          += graphButton_Click;
            this.graphPictureBox.MouseClick += graphPictureBox_MouseClick;
            this.graphPictureBox.Paint      += graphPictureBox_Paint;

            #endregion
        }

        #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)
        {
            RectangleF worldRectangle = new RectangleF
            (
                X_MINIMUM,
                Y_MINIMUM,
                X_MAXIMUM - X_MINIMUM,
                Y_MAXIMUM - Y_MINIMUM
            );

            PointF[] pointArray =
            {
                new PointF(0, graphPictureBox.ClientSize.Height),
                new PointF(graphPictureBox.ClientSize.Width, graphPictureBox.ClientSize.Height),
                new PointF(0, 0),
            };

            this.drawingTransform = new Matrix(worldRectangle, pointArray);

            this.inverseTransform = this.drawingTransform.Clone();

            this.inverseTransform.Invert();
        }

        #endregion
        #region 계산하기 버튼 클릭시 처리하기 - calculateButton_Click(sender, e)

        /// <summary>
        /// 계산하기 버튼 클릭시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void calculateButton_Click(object sender, EventArgs e)
        {
            Cursor = Cursors.WaitCursor;

            this.aTextBox.Clear();
            this.bTextBox.Clear();
            this.errorTextBox.Clear();

            Application.DoEvents();

            MathHelper.GetLinearLeastSquaresFit(this.pointList, out this.bestA, out this.bestB);

            this.hasSolution = true;

            this.aTextBox.Text = this.bestA.ToString();
            this.bTextBox.Text = this.bestB.ToString();

            ShowError();

            this.hasSolution = true;

            this.graphPictureBox.Refresh();

            Cursor = Cursors.Default;
        }

        #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.pointList = new List<PointF>();

            this.hasSolution = false;

            this.graphPictureBox.Refresh();

            this.aTextBox.Clear();
            this.bTextBox.Clear();
            this.errorTextBox.Clear();
        }

        #endregion
        #region 그래프 버튼 클릭시 처리하기 - graphButton_Click(sender, e)

        /// <summary>
        /// 그래프 버튼 클릭시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void graphButton_Click(object sender, EventArgs e)
        {
            this.bestA = double.Parse(this.aTextBox.Text);
            this.bestB = double.Parse(this.bTextBox.Text);

            ShowError();

            this.graphPictureBox.Refresh();
        }

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

        /// <summary>
        /// 그래프 픽처 박스 마우스 클릭시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void graphPictureBox_MouseClick(object sender, MouseEventArgs e)
        {
            PointF[] pointArray =
            {
                new PointF(e.X, e.Y)
            };

            this.inverseTransform.TransformPoints(pointArray);

            this.pointList.Add(pointArray[0]);

            this.graphPictureBox.Refresh();
        }

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

        /// <summary>
        /// 그래프 픽처 박스 페인트시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void graphPictureBox_Paint(object sender, PaintEventArgs e)
        {
            e.Graphics.Transform = this.drawingTransform;

            DrawAxe(e.Graphics);

            if(this.hasSolution)
            {
                using(Pen pen = new Pen(Color.Blue, 0))
                {
                    double y0 = this.bestA * X_MINIMUM + this.bestB;
                    double y1 = this.bestA * X_MAXIMUM + this.bestB;

                    e.Graphics.DrawLine
                    (
                        pen,
                        (float)X_MINIMUM,
                        (float)y0,
                        (float)X_MAXIMUM,
                        (float)y1
                    );
                }
            }

            float deltaX = (X_MAXIMUM - X_MINIMUM) / 200;
            float deltaY = (Y_MAXIMUM - Y_MINIMUM) / 200;

            using(Pen pen = new Pen(Color.Black, 0))
            {
                foreach(PointF point in this.pointList)
                {
                    e.Graphics.FillRectangle
                    (
                        Brushes.White,
                        point.X - deltaX,
                        point.Y - deltaY,
                        2 * deltaX,
                        2 * deltaY
                    );

                    e.Graphics.DrawRectangle
                    (
                        pen,
                        point.X - deltaX,
                        point.Y - deltaY,
                        2 * deltaX,
                        2 * deltaY
                    );
                }
            }
        }

        #endregion

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

        #region 축 그리기 - DrawAxe(graphics)

        /// <summary>
        /// 축 그리기
        /// </summary>
        /// <param name="graphics">그래픽스</param>
        private void DrawAxe(Graphics graphics)
        {
            using(Pen pen = new Pen(Color.Black, 0))
            {
                const float xThick = 0.2f;
                const float yThick = 0.2f;

                graphics.DrawLine(pen, X_MINIMUM, 0, X_MAXIMUM, 0);

                for(float x = X_MINIMUM; x <= X_MAXIMUM; x += 1.0f)
                {
                    graphics.DrawLine(pen, x, -yThick, x, yThick);
                }

                graphics.DrawLine(pen, 0, Y_MINIMUM, 0, Y_MAXIMUM);

                for(float y = Y_MINIMUM; y <= Y_MAXIMUM; y += 1.0f)
                {
                    graphics.DrawLine(pen, -xThick, y, xThick, y);
                }
            }
        }

        #endregion
        #region 오차 보여주기 - ShowError()

        /// <summary>
        /// 오차 보여주기
        /// </summary>
        private void ShowError()
        {
            double error = Math.Sqrt(MathHelper.GetErrorSquared(this.pointList, this.bestA, this.bestB));

            this.errorTextBox.Text = error.ToString();
        }

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

댓글을 달아 주세요