程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> 關於C# >> 仿查詢分析器的C#計算器——6.函數波形繪制

仿查詢分析器的C#計算器——6.函數波形繪制

編輯:關於C#

最近把計算器完善了一下,添加了變量的支持,添加了更多的函數,把邏輯短路操作也實現了,並修正了一些小錯誤。想起來以前在一本書 裡看到過一個示例,輸入函數表達式,就可以繪制函數的波形。最開始學VB的時候,就喜歡用函數來畫圖。再加上對電子技術有點興趣,很多 波形都可以用函數來表示,很自然就想到用程序來模擬示波器顯示波形。但是因為函數都需要在代碼裡面寫死,如果需要新增函數或者進行修 改,需要修改程序代碼再編譯運行。既然現在可以做到對表達式進行計算,也可以支持變量,那麼讓變量的值變化就可以計算得到不同的值, 再把這些值組合成坐標點,連接起來就成了波形。於是乎,咱也試試做一個顯示函數波形的小程序玩玩,效果如下:

先說說新添加的變量支持功能。這裡的變量並不需要聲明,只要不是保留的關鍵字,程序就把它作為變量。在以前的版本中遇到不認識的字 符串會報錯,現在是在分析關鍵字的時候做了特殊處理,遇到非關鍵字字符串則添加到一個靜態的變量字典中。變量字典的Key是該變量的字符 串表示,Value是一個TokenValue對象。在添加到字典之後,如果再遇到相同的字符串,則返回變量字典中對應的TokenValue對象。下面給個例 子:

從例子可以看出,在未賦值之前,n的值為空,和其他值運算不會發生錯誤。下面是語法樹分析的圖:

從圖上可以看出變量n是引用的,在第一句中n的值是空,類型為未初始化類型,但是在PropertyGrid中顯示的信息是最後一次賦值的結果。 而且這裡把賦值操作符"="作為賦值操作的根節點,並沒有像左括號"("一樣處理。比如最後一個表達式sin(n+20)的語法 樹中,TokenSin的下級是TokenPlus,而不是TokenLeftBracket。對於賦值操作符"="之所以這保留了原始結構,是因為這樣可以在 修改下級節點的值之後繼續調用Execute方法進行計算,否則如果把值直接指定給變量,下次調用Execute的時候就沒法執行了。左括號只是分 割表達式,但賦值操作符是有真正的運算過程,所以必須用不一樣的分析方法。這一點對於下面要實現的函數波形非常重要。在繪制波形的時 候需要改變變量,如果采用變量字符串替換的方法,每次都需要分析表達式,而變量的值域可能很大,這樣會把大量時間消耗在分析上。但是 如果能保留完整的語法樹,只需要將變量對應的TokenRecord的值改變,再次調用頂級節點的Execute方法,這時候只需要逐級向下調用計算方 法即可,不需要重新分析表達式了。

接下來就介紹怎麼實現函數波形繪制的吧。首先這裡引入了一個變量n,在進行計算之前在程序裡面進行初始化,然後根據設置的范圍用for 遞增。定義兩個表達式X和Y,分別對應坐標點的X和Y,這兩個表達式中包含n,在對n進行遞增之前調用語法分析類進行分析,得到頂級節點, 這時候語法樹已經分析完成了。在對n進行遞增的時候,計算X和Y,形成一系列坐標點。調用Graphics類的DrawLines方法,把計算得到的一系 列坐標點作為參數傳遞給該方法,這樣就可以看到特定的波形。

比如阿基米德螺旋線用偽代碼表示如下: 

for(int n = 1; n < 360; n++)
{
    X = n*sin(n);
    Y = n*cos(n);
    PointCollection.Add(new Point(X, Y));
}

myGraphics.DrawLines(myPen, PointCollection);

在本程序裡阿基米德螺旋線的偽代碼可以表示如下:

strN = "n=0";
strX = "n*sin(n)";
strY = "n*cos(n)";
TokenN = mySyntaxAnalyse.Analyse(strN);
TokenX = mySyntaxAnalyse.Analyse(strX);
TokenY = mySyntaxAnalyse.Analyse(strY);

for(int index = 1; n < 360; n++)
{
    TokenN.TokenValue = index;
    TokenX.Execute();
    TokenY.Execute();
    PointCollection.Add(new Point(TokenX.TokenValue, TokenY.TokenValue));
}
myGraphics.DrawLines(myPen, PointCollection);

從偽代碼中可以看到X和Y的表達式可以由用戶輸入,這樣就不需要修改程序再編譯才能顯示要繪制的波形圖了。

為了同時支持多個波形圖,這裡用一個類來記錄一個函數對,以及線條顏色、線條寬度等信息。該類的代碼如下:

/// <summary>
    /// 繪圖信息
    /// </summary>
    public class DrawInfo
    {
        #region 字段和屬性聲明

        private const string CategoryName = "繪圖信息";

        private string m_Name = "未命名項";
        [Category(CategoryName), DisplayName("名稱"), DefaultValue("未命名項"),  MergableProperty(false), Description("繪圖信息的名稱。")]
        public string Name
        {
            get { return m_Name; }
            set
            {
                if (m_Name != value && value.Trim().Length > 0)
                    m_Name = value; 
            }
        }

        private Color m_LineColor = Color.Black;
        [Category(CategoryName), DisplayName("線條顏色"), DefaultValue(typeof(Color),  "Black"), Description("繪制線條的顏色。")]
        public Color LineColor
        {
            get { return m_LineColor; }
            set { m_LineColor = value; }
        }

        private float m_LineWidth = 1.0f;
        [Category(CategoryName), DisplayName("線條寬度"), DefaultValue(1.0f), Description("繪制線條 的寬度(以像素為單位)。")]
        public float LineWidth
        {
            get { return m_LineWidth; }
            set { m_LineWidth = value; }
        }

        private string m_ExpressionX = "n";
        [Category(CategoryName), DisplayName("表達式X"), DefaultValue("n"), Description(" 對應坐標軸X的表達式。")]
        [Editor("System.ComponentModel.Design.MultilineStringEditor, System.Design, Version=2.0.0.0,  Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))]
        public string ExpressionX
        {
            get { return m_ExpressionX; }
            set
            {
                if (m_ExpressionX != value && value.Trim().Length > 0)
                    m_ExpressionX = value.Trim();
            }
        }

        private string m_ExpressionY = "n";
        [Category(CategoryName), DisplayName("表達式Y"), DefaultValue("n"), Description(" 對應坐標軸Y的表達式。")]
        [Editor("System.ComponentModel.Design.MultilineStringEditor, System.Design, Version=2.0.0.0,  Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))]
        public string ExpressionY
        {
            get { return m_ExpressionY; }
            set
            {
                if (m_ExpressionY != value && value.Trim().Length > 0)
                    m_ExpressionY = value.Trim();
            }
        }

        private List<PointF> m_PointList = new List<PointF>();
        /// <summary>
        /// 坐標列表
        /// </summary>
        [Browsable(false)]
        public List<PointF> PointList
        {
            get { return m_PointList; }
        }

        #endregion 字段和屬性聲明

        /// <summary>
        /// 構造函數
        /// </summary>
        public DrawInfo()
        { }


        private TokenRecord m_TokenX;
        private TokenRecord m_TokenY;
        /// <summary>
        /// 初始化記號對象
        /// </summary>
        /// <param name="Analyser">表達式分析計算類的實例</param>
        public void InitialToken(SyntaxAnalyse Analyser)
        {
            if (Analyser != null)
            {
                m_TokenX = Analyser.Analyse(m_ExpressionX.ToLower());
                m_TokenY = Analyser.Analyse(m_ExpressionY.ToLower());
                this.m_PointList.Clear();
            }
        }

        /// <summary>
        /// 執行計算
        /// </summary>
        public void Execute()
        {
            m_TokenX.Execute();
            m_TokenY.Execute();
            m_PointList.Add(new PointF(Convert.ToSingle(m_TokenX.TokenValue), Convert.ToSingle (m_TokenY.TokenValue)));
        }

        public override string ToString()
        {
            return m_Name;
        }

    }//class DrawInfo

在界面上添加相關控件,用來操作繪圖信息。點擊繪圖按鈕之後,按照界面上的PictureBox的尺寸創建一個Bitmap對象,然後把它作為參數 調用繪圖代碼,代碼如下:

private void Draw(Bitmap myImage)
        {
            try
            {
                Graphics g = Graphics.FromImage(myImage);

                SyntaxAnalyse.DicVariable.Clear();
                int intMin = this.numMin.Value < this.numMax.Value ? (int)this.numMin.Value :  (int)this.numMax.Value;
                int intMax = this.numMin.Value < this.numMax.Value ? (int)this.numMax.Value :  (int)this.numMin.Value;

                TokenRecord TokenN = m_Analyse.Analyse("n");
                TokenN.TokenValueType = typeof(double);
                TokenN.TokenValue = intMin;

                //初始化
                foreach (DrawInfo item in this.m_DrawInfoList)
                {
                    item.InitialToken(m_Analyse);
                }


                g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
                //繪制X軸和Y軸
                g.DrawLine(Pens.Black, 0, this.picImage.Height / 2, this.picImage.Width,  this.picImage.Height / 2);
                g.DrawLine(Pens.Black, this.picImage.Width / 2, 0, this.picImage.Width / 2,  this.picImage.Height);
                g.TranslateTransform(Convert.ToSingle(myImage.Width / 2), Convert.ToSingle(myImage.Height  / 2));


                //計算表達式
                for (int intIndex = intMin; intIndex <= intMax; intIndex++)
                {
                    TokenN.TokenValue = (double)intIndex;
                    foreach (DrawInfo item in this.m_DrawInfoList)
                    {
                        item.Execute();
                    }
                }

                //繪制圖像
                foreach (DrawInfo item in this.m_DrawInfoList)
                {
                    g.DrawLines(new Pen(item.LineColor, item.LineWidth), item.PointList.ToArray());
                }
                myImage.RotateFlip(RotateFlipType.Rotate180FlipX);

                //繪制刻度
                SolidBrush myBrush = new SolidBrush(Color.Black);
                for (int intX = 0; intX < myImage.Width / 2; intX += (int) (this.numScale.Value))
                {
                    g.DrawLine(Pens.Black, intX, 0, intX, -3);
                    g.DrawString(intX.ToString(), this.Font, myBrush, intX + 1, 1);
                    if (intX == 0)
                        continue;
                    g.DrawLine(Pens.Black, -intX, 0, -intX, -3);
                    g.DrawString("-" + intX.ToString(), this.Font, myBrush, -intX + 1,  1);
                }

                for (int intY = 0; intY < myImage.Height / 2; intY += (int) this.numScale.Value)
                {
                    g.DrawLine(Pens.Black, 0, -intY, 3, -intY);
                    g.DrawString(intY.ToString(), this.Font, myBrush, 1, -intY + 1);
                    if (intY == 0)
                        continue;
                    g.DrawLine(Pens.Black, 0, intY, 3, intY);
                    g.DrawString("-" + intY.ToString(), this.Font, myBrush, 1, intY +  1);
                }

                //繪制圖示Legend
                g.TranslateTransform(Convert.ToSingle(myImage.Width / 2 * -1), Convert.ToSingle (myImage.Height / 2 * -1));
                int intOffsetX = 10;
                int intOffsetY = 10;
                int intLegendHeight = (int)g.MeasureString("123",this.Font).Height;
                foreach (DrawInfo item in m_DrawInfoList)
                {
                    using (SolidBrush LegendBrush = new SolidBrush(item.LineColor))
                    {
                        g.FillRectangle(LegendBrush, intOffsetX, intOffsetY, 30,  intLegendHeight);
                        g.DrawString(item.Name, this.Font, LegendBrush, intOffsetX + 30 + 5,  intOffsetY);
                        intOffsetY += intLegendHeight + 5;
                    }
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show("錯誤信息為:" + ex.Message, "運算發生錯誤",  MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
        }

這裡的代碼還有不少可以改進的地方,比如可以設置圖片尺寸、圖片背景、坐標原點、背景網格,甚至可以讓波形一段一段慢慢的顯示出來 ,更好的了解波形的繪制過程。如果有需要可以自行完善。

本文開頭給出的示例的各個設置如下表:  名稱 表達式X 表達式Y 相位1 n a=100*sin(n) 相位2 n b=100*sin(n+120) 相位3 n c=100*sin(n-120) 三相整流波形 n abs(a)+abs(b)+abs(c) 李沙育圖 100*sin(n*2) 100*cos(n*3+90)-200 阿基米德螺旋線 n*sin(abs(n))/20-240 n*cos(abs(n))/20-180

相位1、相位2、相位3是模擬三相電的波形,都是標准正弦波,只是相位差120度。這裡用一個賦值操作聲明了三個變量a, b, c,這樣在三 相整流波形中就可以直接操作這三個變量了,所以三相整流波形的表達式Y的值是abs(a)+abs(b)+abs(c)。通過聲明變量的方法可以很容易讓波 形之間關聯起來,也可以減少計算量。

有時候胡亂輸入一些函數,會有一些很好玩的波形出來,下面給一些例子。

繪制波形需要一些GDI+的基礎知識,並不難理解。掌握足夠的GDI+知識之後還可以做出統計圖之類的控件,根據輸入的數據繪制折線圖或者 柱狀圖之類,和這裡的波形圖類似。這裡貼幾張我做的統計圖控件繪制的圖吧,雖然沒法和Dundas之類的相比,但一般應用足夠了。

折線圖

柱狀圖

橫道圖

餅圖

下圖是統計圖中需要繪制的區域注釋,實際繪圖時根據數據分析,然後計算出相關的坐標就可以進行繪圖了。

本文就到此結束了,做的還不是很完善,有需要的朋友可以自行修改一下。

本文配套源碼

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved