Search code examples
c#winformschartsvector-graphics

Is there a way to export a line that was drawn on a chart to an EMF file in Winforms?


I'm drawing some data using Winforms (with a C# solution).

I plot error bars using chart series (with the chart.Series[item].Points.AddXY() method). However, I link the error bars' middle points with a drawing in the Paint event handler, since series doesn't seem to be drawable with custom pens. The Paint event looks like the following (it has some custom shenanigans for offsetting each curve along the x-axis):

private void Chart_Paint(object sender, PaintEventArgs e)
{
    var g = e.Graphics;
    g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
    for (int line = 0; line < _meanLines.Count; line++)
    {
        Pen pen = _pens[line];
        _xOffset = LinesOffset(_meanLines.Count, line, _xOffsetRatio);

        List<(float x, float y)> points = _meanLines[line].Select(mean => (float.Parse(mean.x) * (1 + _xOffset), mean.y)).ToList();
        
        List<Point> pointsList = points.Select(p => new Point((int)chart.ChartAreas[0].AxisX.ValueToPixelPosition(p.x), (int)chart.ChartAreas[0].AxisY.ValueToPixelPosition(p.y))).ToList();

        for (int p = 1; p < pointsList.Count; p++)
        {
            g.DrawLine(pen, pointsList[p - 1], pointsList[p]);
        }
    }
}

Everything works fine, and my chart looks like I want it to look: chart as displayed when the software is running.

I then want to export my chart as an EMF file (to be later be included in LaTex), using: chart.SaveImage($@"C:\Users\User\Documents\DataPlotter\{figureName}.emf", ChartImageFormat.Emf);

But then my lines drawn in the Paint event handler are missing: chart as exported in emf.

Is there a way to keep these lines in an emf export?

I tried exporting the chat as a bitmap to check if the drawn lines were exported, and it worked (the following code saved the aforementioned image with the lines displayed):

using (Bitmap bmp = new Bitmap(chart.Size.Width, chart.Size.Height)) 
            {
                chart.DrawToBitmap(bmp, new Rectangle(0, 0, chart.Size.Width, chart.Size.Height));
                using (Graphics g = Graphics.FromImage(bmp)) 
                {
                    g.DrawLine(new Pen(Color.Black, 2), new Point(0, 0), new Point(chart.Size.Width, chart.Size.Height));
                    chart.SaveImage($@"C:\Users\User\Documents\DataPlotter\{figureName}_trueEmf.emf", ChartImageFormat.Emf); // The drawn lines are missing
                    bmp.Save($@"C:\Users\User\Documents\DataPlotter\{figureName}_png.png", System.Drawing.Imaging.ImageFormat.Png); // Every drawn line appear
                    bmp.Save($@"C:\Users\User\Documents\DataPlotter\{figureName}_bmpEmf.emf", System.Drawing.Imaging.ImageFormat.Emf); // Every drawn line appear but saved as a bitmap
                } 
            }

But I still can't find a way to export these lines in a vectorial image. I can export an emf file using the last line with the bmp.Save method, but it's not drawn using vectors (I can't import it to Inkscape and save an eps file for instance).

I feel like there's no way to make DrawLine() draw vectors, but I don't know if .NET provides something else for drawing vectors (appart from using a chart.Series object, but I can't find a way to do so while using a custom pen...

Does anyone have an idea?

Thanks!


Solution

  • Ok, finally I worked it out, this is the most simple solution I could come up with.

    First, refactor your Chart_Paint method so you can reuse the drawing of those additional lines outside of the Paint event. It should look like this:

    private void Chart_Paint(object sender, PaintEventArgs e)
    {
        DrawExtraGraphics(e.Graphics);
    }
    

    Now, we can save the chart to a temporary memory stream, read that stream again and overlay the drawing with the extra lines:

    MemoryStream ms = new MemoryStream();
    chart.SaveImage(ms, ImageFormat.Emf);
    ms.Seek(0, SeekOrigin.Begin);
    using (var mf0 = new Metafile(ms))
    {
        // "this" can be the form, that will be sufficient
        using (var gfx = this.CreateGraphics())
        {
            // this creates an empty EMF file
            using (var mf = new Metafile(MyFilename, gfx.GetHdc()))
            {
                // gfx context for drawing in the file
                using (var igfx = Graphics.FromImage(mf))
                {
                    // draw the chart
                    igfx.DrawImage(mf0, 0, 0);
                    // draw the extra lines
                    DrawExtraGraphics(igfx);
                }
            }
        }
    }
    

    (I found the code for creating and saving a new EMF here.)

    The result is an EMF which contains the chart as well as the extra lines as vector elements. Hope this helps!