Function plotting in C#

ddanbe 0 Tallied Votes 4K Views Share

When you want to draw something on a form you need a Graphics object.
But a Graphics constructor isn’t public and if that is not enough the Graphics class is also sealed! This means you can’t create a Graphics object via the new keyword nor can you derive from the Graphics class. One way to get a Graphics object is through a Paint event handler of a Form via the PaintEventArgs class.
OK now you can use an array of Points, pass it with a Pen object to DrawLines and it draws your function!
This is not what I want here. I want my functions to plot a single dot, not connected by a line. That seems hard to do in GDI+, if anyone knows a better answer then what I got here please let me know. The PlotPixel method of my Plot class is something I found on the internet, which I feed with transformed pixelcoordinates so it looks nice on the screen.
I exercice my Plot class in a DrawIt class which is derived from Form. In the DrawWindow_Paint event handler two functions are plotted: a classic sine function and a function which simulates the trajectories of particles in a cyclotron. Change some of the values a,b,c,x and y to get amazing results!
Also notice the same minimalistic approach as used in a previous entry in code snippets.

using System;
using System.Drawing;

namespace SimpleDrawProject
{
    class Plot
    {
        //class to plot x and y values on a form
        //
        //because on the form coordinates start at the upper left corner with 0,0
        //with the y coordinate going down, a little transformation is done here
        //so that x,y coordinates act as normal carthesian coordinates, with 0,0
        //in the center of the form

        struct PlotPort
        {
            public int minX;
            public int maxX;
            public int minY;
            public int maxY;
        };

        private PlotPort _PlotW;    //"window" of carthesian coordinates
        private Size _ClientArea;   //keeps the pixels info
        private double _Xspan;
        private double _Yspan;

        public Plot(){}

        public Plot(Size Plotarea) 
        {
            _ClientArea = Plotarea;
        }

        public Size ClientArea { set { _ClientArea = value; } }

        public void SetPlotPort(int minx, int maxx, int miny, int maxy)
        {
            //set the bounderies of the form(screen) to real coordinates.
            _PlotW.minX = minx;
            _PlotW.maxX = maxx;
            _PlotW.minY = miny;
            _PlotW.maxY = maxy;
            _Xspan = _PlotW.maxX - _PlotW.minX;
            _Yspan = _PlotW.maxY - _PlotW.minY;
        }

        public void PlotPixel(double X, double Y, Color C, Graphics G)
        {
            //workhorse of this class
            Bitmap bm = new Bitmap(1, 1);
            bm.SetPixel(0, 0, C);
            G.DrawImageUnscaled(bm, TX(X), TY(Y));
        }

        private int TX(double X) //transform real coordinates to pixels for the X-axis
        {
            double w;
            w = _ClientArea.Width / _Xspan * X + _ClientArea.Width / 2;
            return Convert.ToInt32(w);
        }

        private int TY(double Y) //transform real coordinates to pixels for the Y-axis
        {
            double w;
            w = _ClientArea.Height / _Yspan * Y + _ClientArea.Height / 2;
            return Convert.ToInt32(w);
        }
    }
}

//---------------------------------------------------------------------------

using System;
using System.Drawing;
using System.Windows.Forms;

namespace SimpleDrawProject
{
    class DrawWindow : Form
    {
        public DrawWindow() //this constructor draws the form
        {
            InitializeComponent();
        }

        static void Main()
        {
            Application.Run(new DrawWindow()); //start the application
        }

        private void InitializeComponent()
        {          
            this.SuspendLayout();
            // DrawWindow
            this.ClientSize = new System.Drawing.Size(800, 800);
            this.Location = new System.Drawing.Point(10, 10);
            this.Name = "DrawWindow";
            this.Paint += new System.Windows.Forms.PaintEventHandler(this.DrawWindow_Paint);
            this.ResumeLayout(false);
        }

        private void DrawWindow_Paint(object sender, PaintEventArgs e)
        {
            Graphics  Grf = e.Graphics;
            MiraPlot(Grf);
            SinusPlot(Grf);
        }

        private void SinusPlot(Graphics Grf)
        {
            Plot MyPlot = new Plot();
            double x = 0.0;   
            double y = 0.0;    
            MyPlot.ClientArea = this.ClientSize;
            MyPlot.SetPlotPort(-10, 10, -5, 5);
            for (x = -7.0; x < 10.0; x += 0.025)
            {
                y = Math.Sin(x);
                MyPlot.PlotPixel(x ,y , Color.BlueViolet, Grf);
            }
        }

        private void MiraPlot(Graphics Grf)
        {
            Plot MyPlot = new Plot();
            double a = -0.46;
            double b = 0.99;
            double c = 2.0 - 2.0 * a;
            double x = 12.0;    //start value
            double y = 0.0;     //start value
            double z;
            double w = a * x + c * x * x / (1 + x * x);

            MyPlot.ClientArea = this.ClientSize;
            MyPlot.SetPlotPort(-20, 20, -20, 20);
            for (int n = 0; n < 20000; n++)
            {
                MyPlot.PlotPixel(x, y, Color.BlueViolet, Grf);
                z = x;
                x = b * y + w;
                w = a * x + c * x * x / (1 + x * x);
                y = w - z;
            }
        }
    }
}
rminator 0 Junior Poster in Training

That is a piece of this code, just look about it,may be it will be better for you.

private void load_courbes()
424.  {
425.    bool dateLu = false;
426.    XDate axeX;
427.    // ouvre la boite de choix de fichier
428.    OpenFileDialog open_csv = new OpenFileDialog();
429.    open_csv.Filter = "Fichiers de données|*.csv";
430.    open_csv.Title = "Selectionner le fichier à visualiser";
431.    open_csv.ShowDialog();
432.    // récupére le nom et le chemin choisi
433.    if (open_csv.FileName.ToString != "")
434.    {
435.      string FichierALire = open_csv.FileName;
436.      if (System.IO.File.Exists(FichierALire))
437.      {
438.        // on vide les anciens points et on décoche les combos
439.        initform();
440.        try {
441.          StreamReader sr = new StreamReader(FichierALire, System.Text.Encoding.Default);
442.          string ligne = sr.ReadLine();
443.          //(s'en servir pour nommer directement le nom des courbes, plus souple pour le futur)
444.          //--- Traitement du fichier de données ligne par ligne ---
445.          while (!sr.EndOfStream()) {
446.            ligne = sr.ReadLine();
447.            string valeur = ligne.Split(ChrW(59))(0);
448.            System.DateTime x1 = (System.DateTime)valeur;
449.            axeX = x1.ToOADate();
450.            // archive la premiére date lu pour démarrer l'axe X
451.            if (!dateLu)
452.            {
453.              minX = axeX;
454.              dateLu = true;
455.            }
456.
457.            // enregistre les autres points dans les courbes
458.            // poids de la mousse
459.            valeur = ligne.Split(ChrW(59))(1);
460.            if (valeur != "") P1.Add(axeX, (double)valeur); 
461.            // t mousse
462.            valeur = ligne.Split(ChrW(59))(2);
463.            if (valeur != "") T1.Add(axeX, (double)valeur); 
464.            // hr mousse
465.            valeur = ligne.Split(ChrW(59))(3);
466.            if (valeur != "") HR1.Add(axeX, (double)valeur); 
467.            // t four
468.            valeur = ligne.Split(ChrW(59))(4);
469.            if (valeur != "") T2.Add(axeX, (double)valeur); 
470.            // hr four
471.            valeur = ligne.Split(ChrW(59))(5);
472.            if (valeur != "") HR2.Add(axeX, (double)valeur);
ddanbe 2,724 Professional Procrastinator Featured Poster

?
Do you understand all the French used here?
If not, I could translate it for you from French to German.
Rather cool :cool: I guess, considering I'm a Dutch speaking person, writing this in English.
Do you ever look something up in MSDN? Google?
Look here:http://msdn.microsoft.com/en-us/library/613dxh46(v=VS.90).aspx
For valeur = ligne.Split(ChrW(59))(1); wich is VB syntax I guess, you could also use valeur = ligne.Split(Convert.ToChar(59))[1]; in C#.
The code you gave me, has some resemblance with the code I already gave you in filling up a DGV with a text file.
The code you gave me, fills up som lists with values, I guess, but it does not going to draw any graphic.

rminator 0 Junior Poster in Training

if(strncmp(buf,"Rman-LOG",7))
what is the equivalent of that in C# please

ddanbe 2,724 Professional Procrastinator Featured Poster

http://msdn.microsoft.com/en-us/library/eywx8zcx(v=vs.71).aspx
Look up the Compare method of the String class.
This has nothing to do with plotting, why did you not post your question in a new thread?

rminator 0 Junior Poster in Training

Sorry it was my fAULT

NickAustin 0 Newbie Poster

Hi, I have found these posts very useful as I am trying to create a simple graphics library. In the snippet at the top of this page, how would you go about rescaling the graph when the user changes the size of the form? As it stands funny things happen when you try. I have tried to add a SizeChanged event handler but since the Grf Graphics object is local to the Paint handler I haven't had much joy. I am quite new to .NET software developement so please bare this in mind! Thanks! Nick

NickAustin 0 Newbie Poster

At the moment if you increase the size of the form window, it does replot the graph in the newly sized window, but the original plot stays there, overlaid on the new one.

NickAustin 0 Newbie Poster

OK, well I've come up with one way of doing it but I'm not sure if it is ideal.

I have added member variables to the DrawWindow class: _clientwidth and _clientheight.
In the DrawWindow constructor I have initialised both these to 500.
I have added a ResizeEnd event which gets the new ClientSize width and height and sets _clientwidth and _clientheight accordingly before calling InitializeComponent again:

    private void DrawWindow_ResizeEnd(object sender, EventArgs e)
    {
        _clientwidth = this.ClientSize.Width;
        _clientheight = this.ClientSize.Height;
        InitializeComponent();
    }

Is this the best way of doing this? It works, but for some reason after resizing the form, it seems to replot a number of times before it feels happy and stops. I have no idea why!

Any comments would be most welcome!
Thanks,
Nick

NickAustin 0 Newbie Poster

This is what I have done... ignore the added bits that plot the axis. Also my Plot class is now the base class from which various types of plot classes inherit. Most of the original functionality has been moved to my XYPlot class. The rescaling works (notice that I have also had to do a similar thing with _xpos and _ypos) but it is very slow... though this might have more to do with my plot classes rather than the form. Anyway, please please comment :)

using System;
using System.Drawing;
using System.Windows.Forms;
using System.Text;
using System.IO;

namespace PlottingLibrary
{
    public partial class DrawWindow2 : Form
    {
        private int _clientwidth;
        private int _clientheight;
        private int _xpos;
        private int _ypos;

        public DrawWindow2()
        {
            _clientwidth = 500;
            _clientheight = 500;
            _xpos = 10;
            _ypos = 10;
            InitializeComponent();
        }

        private void InitializeComponent()
        {
            this.SuspendLayout();
            // 
            // DrawWindow
            // 
            this.BackColor = System.Drawing.SystemColors.Window;
            this.ClientSize = new System.Drawing.Size(_clientheight, _clientwidth);
            this.Location = new System.Drawing.Point(_xpos, _ypos);
            this.Name = "DrawWindow2";
            this.ShowIcon = false;
            this.Text = "Trig Functions";
            this.Paint += new System.Windows.Forms.PaintEventHandler(this.DrawWindow_Paint);
            this.ResizeEnd += new System.EventHandler(this.DrawWindow_ResizeEnd);
            this.ResumeLayout(false);
        }

        private void DrawWindow_Paint(object sender, PaintEventArgs e)
        {
            Graphics Grf = e.Graphics;
            SinusPlot(Grf);
        }

        private void DrawWindow_ResizeEnd(object sender, EventArgs e)
        {
            _clientwidth = this.ClientSize.Width;
            _clientheight = this.ClientSize.Height;
            _xpos = this.Location.X;
            _ypos = this.Location.Y;
            InitializeComponent();
        }

        private void SinusPlot(Graphics Grf)
        {
            double x = 0.0;
            double y = 0.0;
            double z = 0.0;
            double w = 0.0;

            Plot MyPlot = new XYPlot(ClientSize);

            MyPlot.SetPlotPort(-10, 10, -2, 2);
            MyPlot.MajorUnitX = 1;
            MyPlot.MajorUnitY = 1;
            MyPlot.NTicksPerMajorUnitX = 10;
            MyPlot.NTicksPerMajorUnitY = 10;
            MyPlot.PlotAxis(Grf, Color.Black);
            MyPlot.PlotTitle(Grf, "Trig Functions", Color.Black);
            MyPlot.PlotXAxisLabel(Grf, "x", Color.Black);
            MyPlot.PlotYAxisLabel(Grf, "y", Color.Black);

            for (x = -10.0; x < 10.0; x += 0.025)
            {
                y = Math.Sin(x);
                z = Math.Cos(x);
                w = Math.Tan(x);
                MyPlot.PlotPixel(Grf, x, y, Color.Blue);
                MyPlot.PlotPixel(Grf, x, z, Color.Red);
                MyPlot.PlotPixel(Grf, x, w, Color.Green);
            }

            LegendEntry sinxLegEntry = new LegendEntry();
            sinxLegEntry.Label = "y = sin(x)";
            sinxLegEntry.Color = Color.BlueViolet;

            LegendEntry cosxLegEntry = new LegendEntry();
            cosxLegEntry.Label = "y = cos(x)";
            cosxLegEntry.Color = Color.Red;

            LegendEntry tanxLegEntry = new LegendEntry();
            tanxLegEntry.Label = "y = tan(x)";
            tanxLegEntry.Color = Color.Green;

            Legend legend = new Legend();
            legend.Color_Box = Color.Black;
            legend.BoxWidth = 120;
            legend.FontSize_Entry = 12;
            legend.Title = "Legend";
            legend.LineHeight = 15;
            legend.Color_Title = Color.Black;
            legend.FontSize_Title = 14;
            legend.xPos = 600;
            legend.yPos = 50;
            legend.AddEntry(sinxLegEntry);
            legend.AddEntry(cosxLegEntry);
            legend.AddEntry(tanxLegEntry);
            legend.PlotLegend(Grf);
        }

        private void MiraPlot(Graphics Grf)
        {
            Plot MyPlot = new XYPlot(ClientSize);
            double a = -0.46;
            double b = 0.99;
            double c = 2.0 - 2.0 * a;
            double x = 12.0;    //start value            
            double y = 0.0;     //start value            
            double z;
            double w = a * x + c * x * x / (1 + x * x);
            MyPlot.ClientArea = this.ClientSize;
            MyPlot.SetPlotPort(-20, 20, -20, 20);
            for (int n = 0; n < 20000; n++)
            {
                MyPlot.PlotPixel(Grf, x, y, Color.BlueViolet);
                z = x;
                x = b * y + w;
                w = a * x + c * x * x / (1 + x * x);
                y = w - z;
            }
        }
    }
}
NickAustin 0 Newbie Poster

The above code produces warnings:
The variable '_clientwidth' is either undeclared or was never assigned.
The variable '_xpos' is either undeclared or was never assigned.

I'm not totally sure why though, as they are both declared and assigned. I assume it's because if the constructor is not called then they won't be assigned. There must be a better way of doing this!

NickAustin 0 Newbie Poster

Is there something special about the InitializeComponent method? Only, it doesn't complain about the use of those variables in other functions.

NickAustin 0 Newbie Poster

OK, I think I have answered some of my own questions...

Of course I was getting a warning for those variables not being set. This wasn't a problem when running the code, but rather when displaying the form in the designer... it had no idea what those values would be set to.

Secondly, the reason why it refreshed the graph multiple times after resizing the window was because when I recalled InitializeComponent() it was recalling both event handlers, and got itself into a bit of a silly loop. To quote a famous cartoon character... D'oh!

So now I have got this:

using System;
using System.Drawing;
using System.Windows.Forms;
using System.Text;
using System.IO;

namespace PlottingLibrary
{
    public partial class DrawWindow2 : Form
    {
        private int _clientwidth;
        private int _clientheight;
        private int _xpos;
        private int _ypos;

        public DrawWindow2()
        {
            _clientwidth = 500;
            _clientheight = 500;
            _xpos = 10;
            _ypos = 10;
            InitializeComponent();
        }

        private void InitializeComponent()
        {
            //this.components = new System.ComponentModel.Container();
            this.SuspendLayout();
            // 
            // DrawWindow
            // 
            this.BackColor = System.Drawing.SystemColors.Window;
            this.ClientSize = new System.Drawing.Size(500, 500);
            this.Location = new System.Drawing.Point(10, 10);
            this.Name = "DrawWindow2";
            this.ShowIcon = false;
            this.Text = "Trig Functions";
            this.Paint += new System.Windows.Forms.PaintEventHandler(this.DrawWindow_Paint);
            this.Resize += new System.EventHandler(this.DrawWindow_Resize);
            this.ResumeLayout(false);
        }

        private void DrawWindow_Paint(object sender, PaintEventArgs e)
        {
            Graphics Grf = e.Graphics;
            SinusPlot(Grf);
        }

        private void DrawWindow_Resize(object sender, EventArgs e)
        {
            _clientwidth = this.ClientSize.Width;
            _clientheight = this.ClientSize.Height;
            _xpos = this.Location.X;
            _ypos = this.Location.Y;
            ResizeOrRePosition();
        }

        private void ResizeOrRePosition()
        {
            this.ClientSize = new System.Drawing.Size(_clientwidth, _clientheight);
            this.Location = new System.Drawing.Point(_xpos, _ypos);
            this.ShowIcon = false;
        }

        private void SinusPlot(Graphics Grf)
        {
            double x = 0.0;
            double y = 0.0;
            double z = 0.0;
            double w = 0.0;

            Plot MyPlot = new XYPlot(ClientSize);

            MyPlot.SetPlotPort(-10, 10, -2, 2);
            MyPlot.MajorUnitX = 1;
            MyPlot.MajorUnitY = 1;
            MyPlot.NTicksPerMajorUnitX = 10;
            MyPlot.NTicksPerMajorUnitY = 10;
            MyPlot.PlotAxis(Grf, Color.Black);
            MyPlot.PlotTitle(Grf, "Trig Functions", Color.Black);
            MyPlot.PlotXAxisLabel(Grf, "x", Color.Black);
            MyPlot.PlotYAxisLabel(Grf, "y", Color.Black);

            for (x = -10.0; x < 10.0; x += 0.025)
            {
                y = Math.Sin(x);
                z = Math.Cos(x);
                w = Math.Tan(x);
                MyPlot.PlotPixel(Grf, x, y, Color.Blue);
                MyPlot.PlotPixel(Grf, x, z, Color.Red);
                MyPlot.PlotPixel(Grf, x, w, Color.Green);
            }

            LegendEntry sinxLegEntry = new LegendEntry();
            sinxLegEntry.Label = "y = sin(x)";
            sinxLegEntry.Color = Color.BlueViolet;

            LegendEntry cosxLegEntry = new LegendEntry();
            cosxLegEntry.Label = "y = cos(x)";
            cosxLegEntry.Color = Color.Red;

            LegendEntry tanxLegEntry = new LegendEntry();
            tanxLegEntry.Label = "y = tan(x)";
            tanxLegEntry.Color = Color.Green;

            Legend legend = new Legend();
            legend.Color_Box = Color.Black;
            legend.BoxWidth = 120;
            legend.FontSize_Entry = 12;
            legend.Title = "Legend";
            legend.LineHeight = 15;
            legend.Color_Title = Color.Black;
            legend.FontSize_Title = 14;
            legend.xPos = 600;
            legend.yPos = 50;
            legend.AddEntry(sinxLegEntry);
            legend.AddEntry(cosxLegEntry);
            legend.AddEntry(tanxLegEntry);
            legend.PlotLegend(Grf);
        }

        private void MiraPlot(Graphics Grf)
        {
            Plot MyPlot = new XYPlot(ClientSize);
            double a = -0.46;
            double b = 0.99;
            double c = 2.0 - 2.0 * a;
            double x = 12.0;    //start value            
            double y = 0.0;     //start value            
            double z;
            double w = a * x + c * x * x / (1 + x * x);
            MyPlot.ClientArea = this.ClientSize;
            MyPlot.SetPlotPort(-20, 20, -20, 20);
            for (int n = 0; n < 20000; n++)
            {
                MyPlot.PlotPixel(Grf, x, y, Color.BlueViolet);
                z = x;
                x = b * y + w;
                w = a * x + c * x * x / (1 + x * x);
                y = w - z;
            }
        }
    }
}

This works well, however I am unsure if this is the ideal way of achieving what I wanted. One weird thing is if I remove the line 'this.ShowIcon = false' from the ResizeOrRePosition() method, the program reverts back to the behaviour of resizing the plot whilst keeping the original sized plot overlaid on the new plot. I have no idea why this should be! Any ideas?

Also, if I have been a complete fool and gone about this in totally the wrong way, please tell me... I won't be offended!

Thanks,
Nick

p.s. sorry for so many posts!

NickAustin 0 Newbie Poster

One last thing... if anyone could also suggest how one might send the plot to a printer that would also be very useful :)

ddanbe 2,724 Professional Procrastinator Featured Poster

Hi Nick!
Appreciate your efforts, my code snippet was just a thing to point out how it could be done, not how it should be done.
Perhaps this can help you also.
And believe me you are by no means a fool. Happy computing :)

skatamatic 371 Practically a Posting Shark

This would work - but there's a few other ways to skin this cat. A line with the start and end points being the same would draw a single pixel. A elipse with size 0 would also draw a pixel. Same with a rectangle.

If you draw your function with sufficient resolution (the distance between points on the x axis) you should be able to draw lines between the points and still have a smooth, good looking function plot as well. A function, by definition, should not have spaces between points - so zooming in on a graph drawn with your method would reveal that it isn't really a function, rather a group of points OF a function. This could be corrected, mind you, if you have sufficient points for a given zoom level or simply re-render the function as it is zoomed.

ddanbe commented: Fine positive remarks! +14
Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.