Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

2D Math Curve Presentation for your Project

0.00/5 (No votes)
7 Oct 2004 1  
Code with comments

Introduction

Perhaps your program requires the graphic presentation of any 2D math expression. In this case you may need a set of classes that will perform this task without complications. Normally you have to think about scaling, layout of the curve, how to set the divisions on the axis, problems with infinities etc. and the graphic representation may become easily an important and tedious part of your project.

Plothelp was created to do all this in the easiest way for you. You just need to introduce 4 lines into your code: one line defines the formula as a string, other two lines define the starting and the endpoint of the x-range and finally you may indicate the number of points to form the curve.

The rest is automatic. Plothelp will scale both axes, set the corresponding divisions, draw the corresponding xy-gridlines and finally draw the curve.

Also, it is often desirable to draw math functions using different signs in order to complete the presentation of the function.

y= sqrt(x) is such an example. Plothelp offers the possibility to use the � operator drawing both options in the same graph. Just write �sqrt(x) instead of sqrt(x) and it will work. You may use this operator in your formula as often as you like.

Formulas as y = �x �1 will result in a graph with 4 curves. The scaling of the y-axis will be adapted to all curves at the same time.

The downloadable demo-exe permits you to write formulas to the console and to represent them by Plothelp. This gives you an impression how the representation would fit in your project.

Using the code

Plothelp is embedded in only three classes, which are dedicated to the layout of the Form, the drawing and the math operations. Also, info.lundin.Math.dll is necessary to parse the formula string. Do the following steps:

  1. In your project, under the Project menu, add the following 3 files:
    • CurveCalc.cs
    • GraphPlot.cs
    • Plotter.cs
  2. Select the Add Reference option and use the Browser option to find the file info.lundin.Math.dll and click OK. This will include the functionality of the formula parser.
  3. Add the directives
    using DataPlotter;
    using System.Windows.Forms;
    using System.Drawing;

    to the header of the class from where you will call Plothelp.

  4. Now you are ready to use Plothelp in your project. Just add the following lines where you need it, p.e:
    // string funcstr="x^2+3*x-4";//defines the function as a string 
    
    // double xs=-8.0; //starting value for the x-range at �8 
    
    // double xe= 3.0; //final value for the x-range at 3 
    
    // int nump=200; //number of points to be drawn //
    
    // Application.Run(new GraphPlot(funcstr,xs,xe,nump)); 

First, the formula is defined as a string taking only the right hand side of the equation. In the above example

y = x^2 + 3*x - 4 is used.

Second, define the range indicating the starting point and the end point as double.

Third, define the number of points to be represented as int. Normally, 100 or 200 points are sufficient. Perhaps in certain cases you may want to study details with better resolution increasing this number.

Finally, call GraphPlot passing the variables exactly as indicated. You should see the graph shown below.

The next graphs show the multiple use of the � operator in the function

y = +sqrt(x)+sin(x)

for the range �1 to 8 when the + sign is replaced successively by the � operator.

Fig 2: :y= sqrt(x) + sin(x) y= sqrt(x) � sin(x) y= �sqrt(x) � sin(x)

How it works

Plothelp produces a matrix for n points of a function:

x0 , y0

x1 , y1

: :

: :

xn-1 , y n-1

However, first the formula is checked for the presence of the � operator in the formula string.

If it exists, it�s position(s) in the string are stored in an array and then successively the operator is replaced by ��� and by �+� in the formula calculating each time a new column in the amplified matrix

x0, y10 �. yk0

x1, y11 �. yk1

: :

: :

xn, y1n .... ykn

where k is twice the number of � operators present in the formula.

Based on the matrix, first for the x-range and then for all y-ranges, the divisions (ticmarks) of the axes are calculated and stored in arrays. Then the axes with the ticmarks are drawn and labelled.

The values for each ticmark are first checked by a special method Rndhlp in order to avoid rounding errors (as p.e. 2.3650000000001*E-13) and works also for exponential notation. Basically, the number is converted into a string, the exponential part �if any- is cut off and the remaining number is treated. If this value is >1 then the sequence of digits is cut at the 5th position behind the coma and the exponential part is added again. However, if the value is <1 then the first digit different from 0 is found and the coma is placed behind. Now again, the sequence is cut at the 5th position behind the coma. As the shift of the coma equals a multiplication by 10n this is taken in account when the possible exponential part is added again.

To draw the curve g.DrawLine(penDraw,Point1,Point2) is used. This means that the entire curve is really a point-to-point construction of tiny straight lines and this may originate some problems in the representation of certain curves. Imagine for example a circle expressed by the function y = sqrt(1-x2) in the range from �2 to +2. As the curve drawing takes place from left to right first it is necessary to identify the point where the curve starts.

This is done by the method FirstNumber() of the class CurveCalc that checks the matrix point by point returning the index of the first position giving a finite y-value. In our example of the circle this should correspond to the point �1,0.

However, if you tried to present the curve in 103 steps for example, the first real point will be between point 25 at x = -1.0196 and the next calculated point at x=-0.980 where the function gives already a value. That means that you did not find the true start of the curve. In such a case you would obtain a circle that is �open� at the beginning (and also at the end).

To avoid such type of problems the method CurveStart(int column) is used. The method looks for the first x-value giving a finite y-value (firstX) and starts iteration between this point and the previous point to find the start of the curve.

 //This method looks for the start of the curve 

 //which is not necessarily at

 //the first point of the range. Also, the start of 

 //the curve may be at infinity;

 //in this case the next point is chosen. 

 //A value of the x-range is returned.

 //"column" corresponds to the number of y-column in the xyMatrix

 public double CurveStart(int column)
 {
      int j,t=column;
     double firstX=0,foreX=0, z=0;
     double increm=(xn-x0)/nump;
     double y;
     bool ynum, yinf; 
     y=curve[0,t]; //y at the start of the range 

     ynum=Double.IsNaN(y); //ynum=false if this value is a number

     yinf=Double.IsInfinity(y); //yinf=true if this value is �8

 
     if(ynum==false & yinf==false)//if the first value is 

 // a number and not infinit

     {
         return x0; //then no problem exists. Returns x0.

     }
     else //if not, now the first x-value giving

     { //a finit y-value is to be found in this column 

       for(j=1;j<=nump-1;j++)
       {
        y=curve[j,t]; //y-value at jth position of the matrix

        ynum=Double.IsNaN(y);     //is that a number? (false for any number)

        yinf=Double.IsInfinity(y);//is that infinit? (false if not infinit)

 
         if(ynum==false & yinf==false)//if this is a finit number:

         {
          firstX=curve[j,0]; //firstX is the first x-value that

           foreX=curve[j-1,0]; //gives a finit y-value! foreX is the 

           break; //previous x-value in the x-range

         } 
       }
 
    }
    if(yinf==false)   
//The curve must start between foreX and firstX and the 

    {                   //starting point is found by iteration

      for(int k=0;k<50;k++)//starts an iteration for 20 times

       {
        z=foreX+(firstX-foreX)/2;
//calculates an average between both values and 

        if(Double.IsNaN(yValue(z,t))==true)
          {foreX=z;}//proves if an y-value exists

        else {firstX=z;} 
      }
    }
    return firstX; 
//returns the aproximated x-value of the start of the curve

 }

For certain type of functions an increase of the iteration factor k may be useful.

In a similar manner the method CurveEnd(int column) is used for the end of the circle. column refers to the column in the xy-matrix that corresponds to the current curve.

Limitations

There are also some rare cases where Plothelp cannot work properly. This happens for example when the curve forms several closed regions within given the x-range. Look on the following graphs representing the function

y = �sin(x)2/x

Fig 3: x-range from 6 to 10 x-range from 6 to 16

As you may see the methods looking for the start and the end of the region do not work between regions and for this reason the last point of the first region is connected to the first point of the second region by a straight line that does not represent the function.

Points of Interest

Did you ever try to program automatic division of the axis? It looks easy in the beginning but it took me quite a long time to make it work well. Now it seems so simple!

 //returns an ArrayList containing the ticmarks of the axis

 //using the limits of the x-range (xa and xe)

 public ArrayList AxDiv(double xa, double xs) 
 { 
   if(xa==Double.NegativeInfinity){xa=-100000;}
   if(xs==Double.PositiveInfinity){xs= 100000;} 
 
   double diff=xs-xa; //finds the range

   double num=diff/10; //takes the 10th part

   int redw=(int)Math.Floor(Math.Log10(num));    //finds the next minor

   double tlg=Math.Pow(10,redw); //as a power of 10

 
   ArrayList tic=new ArrayList();
   if(diff/tlg<11){tlg=tlg;} //only 10 divisions are allowed

   else if(diff/(2*tlg)<11){tlg=2*tlg;} //this reduces their number by 2

   else if(diff/(5*tlg)<11){tlg=5*tlg;} //this reduces by 5

   else {tlg=10*tlg;} //and if necessary by 10

 
   if(xa/tlg==Math.Round(xa/tlg)) //only if the starting value of

   { //the range is an entire this value

        tic.Add(xa); //is taken as the first ticmark

   }
   else //otherwise the range is amplified

   { //to the left to the next ticmark

        tic.Add(Math.Floor(xa/tlg)*tlg);
   }
 
   while(Convert.ToDouble(tic[tic.Count-1])<xs)
 //now the rest of the ticmark+s

   { //is added

        tic.Add(Convert.ToDouble(tic[tic.Count-1])+tlg);
   }
   return tic;
 } 

History

This was my first program in C#. I am sure that you may find a lot of things that are improvable. To shorten the effort I took a part of a nice program written by Hans-J�rgen Schmidt: DataPlotter - linear or logarithmic display of 2D data which has been available through the CodeProject.

Another great help was the formula parser written by Patrik Lundin (see http://www.lundin.info) which works very well and saved me a lot of time. If you want to be informed about the rules of the syntax of the formula parser it�s a good idea to visit this site.

My sincere acknowledgement to both authors.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here