3

I have a robot that outputs x,y,z position in space. My problem is that I can only find 2D plot in windows forms using chart.

I want to plot my robot in 3D space. Any tools I can use??

Something similar to this:

enter image description here

I need a free software solution for this

EDIT:

My 2D graph atm:

        chart1.ChartAreas[0].AxisX.Minimum = 0;
        chart1.ChartAreas[0].AxisX.Maximum = 12;
        chart1.ChartAreas[0].AxisX.Interval = 1;

        chart1.ChartAreas[0].AxisY.Minimum = 0;
        chart1.ChartAreas[0].AxisY.Maximum = 7;
        chart1.ChartAreas[0].AxisY.Interval = 1;

        //example
        posicao_atual_master.X = 10;
        posicao_atual_master.Y = 5;




         chart1.Series[0].Points.Clear();
        chart1.Series[0].Points.AddXY(posicao_atual_master.X, posicao_atual_master.Y);

DESIGNER:

// chart1
        // 
        chartArea1.AxisX.MajorGrid.Enabled = false;
        chartArea1.AxisX.MajorTickMark.Enabled = false;
        chartArea1.AxisY.MajorGrid.Enabled = false;
        chartArea1.AxisY.MajorTickMark.Enabled = false;
        chartArea1.Name = "ChartArea1";
        chartArea1.Position.Auto = false;
        chartArea1.Position.Height = 100F;
        chartArea1.Position.Width = 90F;
        this.chart1.ChartAreas.Add(chartArea1);
        legend1.BackColor = System.Drawing.Color.Transparent;
        legend1.BorderColor = System.Drawing.Color.Transparent;
        legend1.Font = new System.Drawing.Font("Microsoft Sans Serif", 4F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Millimeter, ((byte)(1)), true);
        legend1.IsTextAutoFit = false;
        legend1.Name = "legen";
        legend1.TableStyle = System.Windows.Forms.DataVisualization.Charting.LegendTableStyle.Tall;
        this.chart1.Legends.Add(legend1);
        this.chart1.Location = new System.Drawing.Point(543, 49);
        this.chart1.Name = "chart1";
        series1.ChartArea = "ChartArea1";
        series1.ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.Point;
        series1.Color = System.Drawing.Color.Transparent;
        series1.Legend = "legen";
        series1.MarkerBorderColor = System.Drawing.Color.Black;
        series1.MarkerImage = "C:\\Users\\Tiago\\Desktop\\CODIGO_TESE_FINAL_BACKUP1408_BOM\\C# - AR.Drone SDK\\AR.Dron" +
"e\\icone_drone_verde.png";
        series1.MarkerImageTransparentColor = System.Drawing.Color.Red;
        series1.Name = "Master";
        series2.ChartArea = "ChartArea1";
        series2.ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.Point;
        series2.Legend = "legen";
        series2.MarkerImage = "C:\\Users\\Tiago\\Desktop\\CODIGO_TESE_FINAL_BACKUP1408_BOM\\Fotos dos Relatórios\\icon" +
"e_drone_vermelho.png";
        series2.Name = "Slave";
        this.chart1.Series.Add(series1);
        this.chart1.Series.Add(series2);
        this.chart1.Size = new System.Drawing.Size(1159, 359);
        this.chart1.TabIndex = 7;
        this.chart1.Text = "chart1";
        this.chart1.MouseDown += new System.Windows.Forms.MouseEventHandler(this.chart1_MouseDown);
        this.chart1.MouseMove += new System.Windows.Forms.MouseEventHandler(this.chart1_MouseMove);
        this.chart1.MouseUp += new System.Windows.Forms.MouseEventHandler(this.chart1_MouseUp);

EDIT: IMAGE

  • Possible duplicate of [How to create a 3D scatter plot?](http://stackoverflow.com/questions/6469247/how-to-create-a-3d-scatter-plot) – Jens Oct 25 '16 at 16:09
  • 1
    It's not duplicate. ILNumerics is not a free tool. –  Oct 25 '16 at 16:10
  • 1
    It is possible to use the Chart control and a little math. Can you show your code so far? – TaW Oct 25 '16 at 16:11
  • ince 2011 ILNumerics has been sold under a proprietary license which starts at 89€ (monthly payment) or 1.300€ (annual payment). 1300€ to plot a robot in space.... yeah no. –  Oct 25 '16 at 16:12
  • TaW ok. I will post. –  Oct 25 '16 at 16:12
  • I posted my code. Atm my chart is 2D and I cant represent Z. –  Oct 25 '16 at 16:18
  • TaW are you there? –  Nov 09 '16 at 15:10
  • @TaW are you there? –  Nov 09 '16 at 15:11
  • yup, here I am. – TaW Nov 09 '16 at 15:14
  • I'm using a button to change the chart from 3D to 2D. My problem is that I try to send to 2,2 and it doesnt stop on 2,2 and then send to 4,2 and makes a weird intersection with the other line. –  Nov 09 '16 at 15:28
  • https://gyazo.com/776e24e16ff07329a8c4cc3f4d1c5d77 –  Nov 09 '16 at 15:28
  • it also doesnt start on 0,0. It starts like 0.2,0 –  Nov 09 '16 at 15:28
  • Hm, changing from 3d to 2d will not be a simple thing and you will have to rewrite basically everything: from the chartytype to the way how datapoints are added to the paint method.. I'm not sure I wouldn't either switch to a new chart or at least to a 2nd chartarea.. – TaW Nov 09 '16 at 16:23
  • I will post a simple new question that will solve my problem. I link it here. –  Nov 09 '16 at 16:48

1 Answers1

4

You are correct, there is no proper way to use a real z-axis in the Chart control.

It does have a 3D style though, which can be used for a reasonably nice ChartArea.

You will have to do the painting of the graph in code though, as the built-in z-axis only support as many, or rather as few discret values as you have Series in the chart.

This is ok for some things, like a color cube, but when you need arbitryry data values it just won't do.

Instead you can do this:

  • Store the z-value of each DataPoint along with the Y-value in the YValues array.
  • For this you need a ChartType that supports several YValues
  • Code one of the xxxPaint events to draw the graphics
  • For this you need a conversion from values to pixels

enter image description here

First we prepare the chart. Many details are up to your needs;

void prepare3dChart(Chart chart, ChartArea ca)
{
    ca.Area3DStyle.Enable3D = true;  // set the chartarea to 3D!
    ca.AxisX.Minimum = -250;
    ca.AxisY.Minimum = -250;
    ca.AxisX.Maximum = 250;
    ca.AxisY.Maximum = 250;
    ca.AxisX.Interval = 50;
    ca.AxisY.Interval = 50;
    ca.AxisX.Title = "X-Achse";
    ca.AxisY.Title = "Y-Achse";
    ca.AxisX.MajorGrid.Interval = 250;
    ca.AxisY.MajorGrid.Interval = 250;
    ca.AxisX.MinorGrid.Enabled = true;
    ca.AxisY.MinorGrid.Enabled = true;
    ca.AxisX.MinorGrid.Interval = 50;
    ca.AxisY.MinorGrid.Interval = 50;
    ca.AxisX.MinorGrid.LineColor = Color.LightSlateGray;
    ca.AxisY.MinorGrid.LineColor = Color.LightSlateGray;

    // we add two series:
    chart.Series.Clear();
    for (int i = 0; i < 2; i++)
    {
        Series s = chart.Series.Add("S" + i.ToString("00"));
        s.ChartType = SeriesChartType.Bubble;   // this ChartType has a YValue array
        s.MarkerStyle = MarkerStyle.Circle;
        s["PixelPointWidth"] = "100";
        s["PixelPointGapDepth"] = "1";
    }
    chart.ApplyPaletteColors();

    addTestData(chart);
}

Here we add some test data:

void addTestData(Chart chart)
{
    Random rnd = new Random(9);
    for (int i = 0; i < 100; i++)
    {
        double x = Math.Cos(i/10f )*88 + rnd.Next(5);
        double y = Math.Sin(i/11f)*88 + rnd.Next(5);
        double z = Math.Sqrt(i*2f)*88 + rnd.Next(5);

        AddXY3d( chart.Series[0], x, y, z);
        AddXY3d( chart.Series[1], x-111, y-222, z);
    }
}

The DataPoints are added with this routine:

int AddXY3d(Series s, double xVal, double yVal, double zVal)
{
    int p = s.Points.AddXY(xVal, yVal, zVal);
    // the DataPoint are transparent to the regular chart drawing:
    s.Points[p].Color = Color.Transparent;
    return p;
}

If this Paint event we draw the data as we like it. Here are either Lines or Points:

private void chart1_PostPaint(object sender, ChartPaintEventArgs e)
{
    Chart chart = sender as Chart;

    if (chart .Series.Count < 1) return;
    if (chart .Series[0].Points.Count < 1) return;

    ChartArea ca = chart .ChartAreas[0];
    e.ChartGraphics.Graphics.SmoothingMode = SmoothingMode.AntiAlias;

    List<List<PointF>> data = new List<List<PointF>>();
    foreach (Series s in chart .Series)
        data.Add(GetPointsFrom3D(ca, s, s.Points.ToList(), e.ChartGraphics));

    renderLines(data, e.ChartGraphics.Graphics, chart , true);  // pick one!
    renderPoints(data, e.ChartGraphics.Graphics, chart , 6);   // pick one!
}

The coodinates are calculated using axis methods:

List<PointF> GetPointsFrom3D(ChartArea ca, Series s, 
                             List<DataPoint> dPoints, ChartGraphics cg)
{
    var p3t = dPoints.Select(x => new Point3D((float)ca.AxisX.ValueToPosition(x.XValue),
        (float)ca.AxisY.ValueToPosition(x.YValues[0]),
        (float)ca.AxisY.ValueToPosition(x.YValues[1]))).ToArray();
    ca.TransformPoints(p3t.ToArray());

    return p3t.Select(x => cg.GetAbsolutePoint(new PointF(x.X, x.Y))).ToList();
}

The actual drawing happens in these routines; one draws lines the other dots:

void renderLines(List<List<PointF>> data, Graphics graphics, Chart chart, bool curves)
{
    for (int i = 0; i < chart.Series.Count; i++)
    {
      if (data[i].Count > 1)
         using (Pen pen = new Pen(Color.FromArgb(64, chart.Series[i].Color), 2.5f))
            if (curves) graphics.DrawCurve(pen, data[i].ToArray());
            else graphics.DrawLines(pen, data[i].ToArray());
    }
}

void renderPoints(List<List<PointF>> data, Graphics graphics, Chart chart, float width)
{
    for (int s = 0; s < chart.Series.Count; s++)
    {
        Series S = chart.Series[s];
        for (int p = 0; p < S.Points.Count; p++)
            using (SolidBrush brush = new SolidBrush(Color.FromArgb(64, S.Color)))
                graphics.FillEllipse(brush, data[s][p].X-width/2, 
                                     data[s][p].Y-width/2,width, width);
    }
}

Other drawing routines like meshes or areas can be coded just as well.. Simply add new routines using user GDI+ methods like DrawCurve or FillPolygon or maybe even DrawImage..

You can set the ChartArea.Area3DStyle.Rotation and the ChartArea.Area3DStyle.Inclination for different views, as can be seen in the animation.

Edit I have update the PostPaint method to minimze dependencies.

enter image description here

TaW
  • 53,122
  • 8
  • 69
  • 111
  • Thanks. If you can provide some code because I'm not very good at C# WindowsForms. –  Oct 25 '16 at 16:31
  • Btw As you can see I'm using MarkerImage to draw a drone image instead of a dot. Do you think I can do something like that with ur approach? –  Oct 25 '16 at 16:32
  • The code uses all the regular GDI+ methods, so, yes it can draw the Images you used for the Markers. But drawing lots of them will cluster the result.. – TaW Oct 25 '16 at 16:36
  • ok Thanks. Have you ever tried to send to values from visual studio to Matlab and plot using Matlab? Please provide the code. I can pay you if you want to develop this simple graph to represent the robot in space. –  Oct 25 '16 at 16:38
  • Nah, this is SO, all for the fun of giving help. And, no, I don't do MatLab. I'll be afk for an hour or two. I'll post the code later.. – TaW Oct 25 '16 at 16:41
  • Thanks. I will wait. –  Oct 25 '16 at 16:42
  • You really like your graphs. – LarsTech Oct 25 '16 at 17:55
  • TaW are you there? –  Oct 31 '16 at 19:37
  • Yeah :D I tried to run the code but I think I'm forgetting some code in the main because it doesnt run. I will post my code below. –  Oct 31 '16 at 19:39
  • I tried to call prepare3dChart(Chart chart, ChartArea ca) below InitializeComponent(); but I dont know what to write. –  Oct 31 '16 at 19:40
  • I uploaded an image of my current form. –  Oct 31 '16 at 19:41
  • I mean I dont know what to use in the arguments (Chart chart, ChartArea ca) –  Oct 31 '16 at 19:46
  • I don't see where you actually call prepare3dChart. The call is simple:You tell the function which chartarea of which chart should be prepared, e.g.: `prepare3dChart(chart1, chart1.ChartAreas[0]);` But: Posting the current code in your question is fine, but an answer should be an answer! In fact you only posted my code, which makes no sense; please delete! – TaW Oct 31 '16 at 19:46
  • renderLines(data, e.ChartGraphics.Graphics, chart3D, true); // pick one! renderPoints(data, e.ChartGraphics.Graphics, chart3D, 6); // pick one! Which one should I use? –  Oct 31 '16 at 19:52
  • Depends on what you want. Do test both! You can see examples how adding points in the `addTestData` method. – TaW Oct 31 '16 at 19:57
  • I dont understand what's ca,chart3d and chart. Which one should I name my chart on the windowsforms. –  Oct 31 '16 at 19:59
  • I think it's not working because it's not calling chart3D_PostPaint –  Oct 31 '16 at 20:12
  • Whoops, a typo. As yopu can see from the event name my chart was named chart3D. Since yours is called chart1, that's what you wuld use. but actually the code can be improved to minimze the dependencies. Please use the new version! Yes, you need to [hook up](http://stackoverflow.com/questions/33275763/copy-datagridview-values-to-textbox/33276161?s=14|0.0000#33276161) the postpaint event of your chart! – TaW Oct 31 '16 at 20:13
  • Makes sence. But it gave me an error. "Cannot use local variable chart1 before it is declared. The declaration of the local variable hides the field _3D.chart'. –  Oct 31 '16 at 20:24
  • On this: if (chart1.Series.Count < 1) return; if (chart1.Series[0].Points.Count < 1) return; –  Oct 31 '16 at 20:24
  • Thank you!!! it's working now. I will have to mess up a bit with it to integrate with my project. This looks good. –  Oct 31 '16 at 20:30
  • I will reply after I do that. Thank you so much for ur help! –  Oct 31 '16 at 20:31
  • btw, I can use the Market Image to make a simple drone instead of the point? Do you think it wont look good? Something like this: https://www.youtube.com/watch?v=woIerYDMQf8 –  Oct 31 '16 at 20:33
  • Last question I ask I swear ;) –  Oct 31 '16 at 20:33
  • Do you know why I lose the Z axis when I add only 1 point? AddXY3d(chart.Series[0], 10, 20, 200); I posted the image above. –  Oct 31 '16 at 20:40
  • Do you use renderLines? It needs two points. Markers are not used but GDI DrawXX calls. So, yes you can write a RenderImages and use DrawImage, but drawing lots of them will clutter the result. Doing it as an animation is a different matter as may well look fine. – TaW Oct 31 '16 at 20:42
  • Hm, yes looks like you need two points. Not sure why, but it may be a requirement to have at least two different x-values in its points. not sure why.. – TaW Oct 31 '16 at 20:54
  • Do you know the values of ChartArea.Area3DStyle.Rotation and the ChartArea.Area3DStyle.Inclination in the last second of the first GIF image you showed? –  Oct 31 '16 at 21:51
  • No, but Inclination can go from -90 to 90° and Rotation from -180 to 180°. – TaW Oct 31 '16 at 21:53
  • Thanks. I will try. –  Oct 31 '16 at 21:58
  • @TaW are you there? –  Nov 09 '16 at 15:11
  • https://i.stack.imgur.com/2Twso.png :( It seems to me that something is wrong with this sales dynamics – xSx Dec 26 '17 at 13:35
  • You may be seeing the spline artifacts you get from having sharp spikes in the data. This happens with all curves even in 2d.. – TaW Dec 26 '17 at 14:30
  • This is brilliant. Works out of the box. Wouldn't have had a scooby how to approach this problem without your answer. – user4109 Nov 07 '19 at 09:34