Search code examples
c#.netmicrosoft-chart-controls

How to save C# Control plus extra text to disk?


I have a C# program that is a console application, but makes use of a Chart object from System.Windows.Forms.DataVisualization.Charting, without attaching the Chart to a Form.

Basically I need to generate a doughnut graph on the Chart control, print some text inside the centre of the doughnut, then save the graph plus the text into a file on disk.

If I create a TextAnnotation in the PrePaint event, then save the chart to disk with DrawToBitmap, the overlaid text is not shown in the file on disk, even if I use various combinations of Chart.Flush and Chart.Update, etc. From what I can tell, the event is being fired and the TextAnnotation code is being run.

On the other hand, if I don't use events at all, instead get a Graphics object from Chart.CreateGraphics, then Graphics.DrawString to print the text, then Graphics.Flush and Chart.DrawToBitmap, the text is still not displayed.

I am guessing that this is because Chart.DrawToBitmap doesn't know about the extra stuff drawn with Chart.CreateGraphics, even though I assumed Graphics.Flush would take care of that.

What would be the best way to save the graph and the text?

EDIT: Added code as requested:

static void Main(string[] args)
{
    String[] labels = { "Green", "Red", "Yellow", "Dark Red", "Blue" };
    Color[] colours = { Color.Green, Color.Red, Color.Yellow, Color.DarkRed, Color.Blue };
    Random rng = new Random();

    Chart chart = new Chart();
    chart.Size = new Size(320, 320);

    ChartArea area = new ChartArea();
    Series series = new Series();

    chart.ChartAreas.Add(area);

    series.ChartArea = area.Name;
    series.ChartType = SeriesChartType.Doughnut;
    series.IsValueShownAsLabel = true;

    int total = 0;
    for (int i = 0; i != labels.Length; i++)
    {
        int value = rng.Next(0, 50);
        DataPoint p = new DataPoint(0, value);
        total += value;
        p.Color = colours[i];
        p.Label = String.Empty;
        p.Font = new Font("Microsoft Sans Serif", 10, FontStyle.Bold);
        series.Points.Add(p);
    }

    series.Tag = total;
    chart.Series.Add(series);
    chart.Refresh();

    using (Graphics g = chart.CreateGraphics())
    {
        String str = series.Tag.ToString();
        Font font = new Font("Microsoft Sans Serif", 32, FontStyle.Bold);
        SizeF strSize = g.MeasureString(str, font);

        int strX = 100; int strY = 100;

        g.DrawString(str, font, new SolidBrush(Color.Black), strX, strY);
        g.DrawRectangle(new Pen(Color.Black), new Rectangle(strX, strY, (int)strSize.Width, (int)strSize.Height));

        g.Flush();
    }

    String chartFilename = "chart.bmp";
    String chartPath = Path.Combine("C:\\Temp", chartFilename);

    using (Bitmap bmp = new Bitmap(chart.Width, chart.Height))
    {
        chart.DrawToBitmap(bmp, chart.Bounds);
        bmp.Save(chartPath);
    }

    System.Diagnostics.Process.Start("mspaint.exe", chartPath);
}

Solution

  • I suspect the chart is drawing over the top of your label. When you call DrawToBitmap, the chart is only going to consider visuals it knows about and not elements drawn over the top afterwards.

    You need to reverse drawing order. i.e.

    1. Draw the chart into the bitmap
    2. With the bitmap in hand with a pre-rendered chart, draw labels over the top

    Code:

    using (Bitmap bmp = new Bitmap(chart.Width, chart.Height))
    {
        chart.DrawToBitmap(bmp, chart.Bounds); // draw chart into bitmap first!
    
        using (Graphics g = Graphics.FromImage(bmp)) // <--- new
        {
            // now draw label
            String str = series.Tag.ToString();
            Font font = new Font("Microsoft Sans Serif", 32, FontStyle.Bold);
            SizeF strSize = g.MeasureString(str, font);
    
            int strX = 100; int strY = 100;
    
            g.DrawString(str, font, new SolidBrush(Color.Black), strX, strY);
            g.DrawRectangle(new Pen(Color.Black), new Rectangle(strX, strY, (int)strSize.Width, (int)strSize.Height));
    
            g.Flush();
        }
    
        bmp.Save(chartPath);
    }
    

    EDIT: Be sure to follow Jimi's suggestion below in the comments to:

    "reference System.Drawing.Imaging, to have PixelFormat and ImageFormat available. At this moment, the image is saved as with a .bmp extension, while it's a .png file (the default)"