We use cookies to give you the best experience on our website. If you continue to browse, then you agree to our privacy policy and cookie policy. Image for the cookie policy date
close icon

SfChart export problem

Hello,

I have a SfChart which works as a live chart, however I need to export it to a PNG file at a different resolution than the current window (at 1920x1080). The live chart has got shown zoom/pan panel and hidden legend. I need the exact opposite on the export file, though. Also I need to add text annotation to the export file and it needs to be always in the left top corner, inside the chart area. This annotation cannot be present in the live chart. I have resolved these problems with the following code:

public void ChartToImage(SfChart chart, string path, string text = null)
{
    var imageSeriesCollection = new ChartSeriesCollection();

    foreach (var s in SfSeries)
    {
        var clonedSeries = s.GetType().CreateNew() as ChartSeries;

        if (clonedSeries is DefaultLineSeries s2 && s is DefaultLineSeries d2)
        {
            s2.Label = d2.Label;
            s2.Interior = d2.Interior;
            s2.StrokeThickness = d2.StrokeThickness;
            s2.StrokeDashArray = d2.StrokeDashArray;
            s2.IsSeriesVisible = d2.IsSeriesVisible;
            s2.XBindingPath = d2.XBindingPath;
            s2.YBindingPath = d2.YBindingPath;
            s2.ItemsSource = d2.ItemsSource;
            s2.VisibilityOnLegend = Visibility.Collapsed;
            s2.IsSeriesVisible = true;
        }
        else if (clonedSeries is DefaultSeries s1)
        {
            s1.Label = s.Label;
            s1.ItemsSource = s.ItemsSource;
            s1.StrokeThickness = s.StrokeThickness;
            s1.XBindingPath = "X";
            s1.YBindingPath = "Y";
            s1.IsSeriesVisible = s.IsSeriesVisible;
            s1.VisibilityOnLegend = s.IsSeriesVisible ? Visibility.Visible : Visibility.Collapsed;
        }

        imageSeriesCollection.Add(clonedSeries);
    }

    var imageChart = new SfChart()
    {
        Margin = new Thickness(0, 10, 30, 15),
        Background = new SolidColorBrush(Colors.Transparent),
        AreaBackground = new SolidColorBrush(Colors.Transparent),
        Width = 1920,
        Height = 1080,
        PrimaryAxis = new NumericalAxis() { Header = "Wavelength [nm]", Minimum = XAxisMin, Maximum = XAxisMax, SmallTicksPerInterval = 4, MinorGridLineStyle = Application.Current.TryFindResource("gridLineMinor") as Style, MajorGridLineStyle = Application.Current.TryFindResource("gridLineMajor") as Style },
        SecondaryAxis = new NumericalAxis() { Header = "Power [dBm]", Minimum = YAxisMin, Maximum = YAxisMax, SmallTicksPerInterval = 4, MinorGridLineStyle = Application.Current.TryFindResource("gridLineMinor") as Style, MajorGridLineStyle = Application.Current.TryFindResource("gridLineMajor") as Style },
        Legend = new ChartLegend() { CheckBoxVisibility = Visibility.Collapsed, DockPosition = ChartDock.Top, LegendPosition = LegendPosition.Inside },
        Series = imageSeriesCollection
    };

    imageChart.Measure(new Size(imageChart.Width, imageChart.Height));
    imageChart.Arrange(new Rect(0, 0, imageChart.Width, imageChart.Height));
    imageChart.InvokeMethod("RenderSeries");
    imageChart.UpdateLayout();

    var canvas = new Canvas()
    {
        Background = new SolidColorBrush(Colors.White)
    };

    var grid = new Grid();

    canvas.Children.Add(grid);

    grid.Children.Add(imageChart);

    if (text != null)
        grid.Children.Add(new TextBlock()
        {
            Text = text,
            FontFamily = new FontFamily("Arial"),
            FontSize = 17,
            Margin = new Thickness(90, 30, 0, 0)
        });

    canvas.Measure(new Size(imageChart.Width, imageChart.Height));
    canvas.Arrange(new Rect(0, 0, imageChart.Width, imageChart.Height));

    RenderTargetBitmap renderBitmap = new RenderTargetBitmap((int)canvas.ActualWidth,
                                                             (int)canvas.ActualHeight,
                                                             96d,
                                                             96d,
                                                             PixelFormats.Pbgra32);

    renderBitmap.Render(canvas);

    var png = new PngBitmapEncoder();
    png.Frames.Add(BitmapFrame.Create(renderBitmap));
    using (Stream stm = File.Create(path))
    {
        png.Save(stm);
    }
}

As you can see, this code is not as optimal as I would like. I have encountered a problem where I can't use the same series that I am feeding to the live chart because of error System.InvalidOperationException: 'Specified element is already the logical child of another element. Disconnect it first.' , so I am cloning all the series. I am using two types of series and they need to be differentiated. There can be as much as 16 series and they have around 120K points each. This creates some overhead but it is not that big of a problem. However what IS a problem, is that after this function runs, the memory of the chart, series and/or axis are not properly garbage collected. I have identified the memory leak in the unmanaged memory so I can't see where exactly is the problem but I suspect the cloned series are being locked to the chart axis and they can't be freed.
Also, when I use your built in Save function, the chart axis are cropped on the sides and I couldn't find other solution than the code I provided.
Is there any other way to do what I need?

I have attached rar file containing example images. Example 2 is using Save function and Example 1 is using my code.
Thank you.

Attachment: examples_5dcb4884.rar

7 Replies

MK Muneesh Kumar G Syncfusion Team October 31, 2018 06:18 AM UTC

Hi Daniel,  
 
Greetings from Syncfusion, we have analysed your query and you can achieve your requirement without cloning our SfChart. We have achieved this by exporting SfChart’s parent with margin and required text as per the below code snippet.  
 
Code snippet 
  private void Button_Click(object sender, RoutedEventArgs e) 
        { 
            Save("Series Number: example Prefix: example Temparature: 25,8"); 
        } 
 
        private void Save(string text=null) 
        { 
            TextBlock textBlock = null; 
 
            if (text != null) 
            { 
                textBlock = new TextBlock() 
                { 
                    Text = text, 
                    FontFamily = new FontFamily("Arial"), 
                    FontSize = 17, 
                    Margin = new Thickness(90, 30, 0, 0) 
                }; 
                grid.Children.Add(textBlock); 
            } 
 
 
            sfChart.GetType().GetMethod("RenderSeries", BindingFlags.NonPublic | 
                BindingFlags.Instance).Invoke(sfChart, null); 
            sfChart.UpdateLayout(); 
            grid.UpdateLayout(); 
 
            RenderTargetBitmap bmpSource = new RenderTargetBitmap((int)grid.ActualWidth, (int)grid.ActualHeight, 96, 96, PixelFormats.Pbgra32); 
            bmpSource.Render(grid); 
            BitmapEncoder imgEncoder = new PngBitmapEncoder(); 
            imgEncoder.Frames.Add(BitmapFrame.Create(bmpSource)); 
            using (Stream stream = File.Create(@"D:\chart.png")) 
            { 
                imgEncoder.Save(stream); 
            } 
 
            if (textBlock !=null) 
                grid.Children.Remove(textBlock); 
        } 
 
We have prepared a sample based on this, please find the sample from the following location.  
 
 
Output: 
 
 
Exported image 
 
 
Hope it helps.  
 
Regards, 
Muneesh Kumar G. 
 
 



DA Daniel November 1, 2018 05:31 PM UTC

Thank you, however this code does not change the resolution of the export file to 1920x1080 and also the export has to work even when the chart is not currently visible on screen (user is on other tab) and this code exports only empty white image.


MK Muneesh Kumar G Syncfusion Team November 2, 2018 12:07 PM UTC

Hi Daniel, 
 
Thanks for your update, we have resolved this problem by measuring and arranging SfChar to required dimension then save as image with text as per the below code snippet.  
 
Code snippet 
private void Save(string text=null) 
        { 
            textBlock.Text = text; 
 
            grid1.Measure(new Size(width, height)); 
            grid1.Arrange(new Rect(0, 0, width, height)); 
            grid1.UpdateLayout(); 
 
            RenderTargetBitmap bmpSource = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32); 
            bmpSource.Render(grid1); 
            BitmapEncoder imgEncoder = new PngBitmapEncoder(); 
            imgEncoder.Frames.Add(BitmapFrame.Create(bmpSource)); 
            using (Stream stream = File.Create(@"D:\chart.png")) 
            { 
                imgEncoder.Save(stream); 
            } 
 
        } 
 
        private void TabControl_Loaded(object sender, RoutedEventArgs e) 
        { 
 
            ... 
 
 
            sfChart.Measure(new Size(width, height)); 
            sfChart.Arrange(new Rect(0, 0, width, height)); 
            sfChart.GetType().GetMethod("RenderSeries", BindingFlags.NonPublic | 
                BindingFlags.Instance).Invoke(sfChart, null); 
            sfChart.UpdateLayout(); 
 
            SfChart chart = sfChart.Clone() as SfChart; 
            chart.Measure(new Size(width, height)); 
            chart.Arrange(new Rect(0, 0, width, height)); 
            chart.GetType().GetMethod("RenderSeries", BindingFlags.NonPublic | 
                BindingFlags.Instance).Invoke(chart, null); 
            chart.UpdateLayout(); 
 
            grid1.Children.Add(chart); 
 
            textBlock = new TextBlock() 
            { 
                FontFamily = new FontFamily("Arial"), 
                FontSize = 17, 
                Margin = new Thickness(90, 30, 0, 0) 
            }; 
            grid1.Children.Add(textBlock); 
            
        } 
 
Here we have prepared a clone SfChart in loading time itself then save the chart with text in button click with required dimension. It does not affect the visual in output.  
 
We have modified our sample based on this, please find the sample from the following location.  
 
 
Please let us know if you have any queries.  

Regards,
Muneesh Kumar G
 



DA Daniel November 6, 2018 11:23 PM UTC

I have tried the code and if I understand it correctly, the chart is being copied only once at the start of the program. Which means that the exported data is always the same. But, as I stated, the chart I need to export is being constantly updated and it is not populated with any series at the start of the program, hence the series are not going to be included in the copy.

 I have created another solution and I have only one problem with it. It does exactly what I want, but after the first exported image, the legend is getting weird and disappears. Images of this are behavior in attachement.

Code:

private SfChart ImageChart;
private ChartSeriesCollection ImageChartSeries;

private void InitializeImageExport()
{
    ImageChartSeries = new ChartSeriesCollection();

    var gridLineMinor = Application.Current.TryFindResource("gridLineMinor") as Style;
    var gridLineMajor = Application.Current.TryFindResource("gridLineMajor") as Style;
    ImageChart = new SfChart()
    {
        Margin = new Thickness(5, 10, 15, 5),
        PrimaryAxis = new NumericalAxis() { Header = "Wavelength [nm]", Minimum = XAxisMin, Maximum = XAxisMax, SmallTicksPerInterval = 4, MinorGridLineStyle = gridLineMinor, MajorGridLineStyle = gridLineMajor },
        SecondaryAxis = new NumericalAxis() { Header = "Power [dBm]", Minimum = YAxisMin, Maximum = YAxisMax, SmallTicksPerInterval = 4, MinorGridLineStyle = gridLineMinor, MajorGridLineStyle = gridLineMajor },
        Legend = new ChartLegend() { CheckBoxVisibility = Visibility.Collapsed, DockPosition = ChartDock.Top, LegendPosition = LegendPosition.Inside },
        Series = ImageChartSeries
    };
}

public void ChartToImage(SfChart chart, string path, string text = null)
{
    var imageSeriesCollection = new ChartSeriesCollection();
    var white = new SolidColorBrush(Colors.White);
    var width = 1920;
    var height = 1080;

    foreach (FastLineBitmapSeries x in SfSeries)
    {
        var clone = x.InvokeMethodAs(typeof(FastLineBitmapSeries), "CloneSeries", new DependencyObject()) as FastLineBitmapSeries;
        if (clone != null)
            ImageChartSeries.Add(clone);
    }

    var grid = new Grid() { Background = white, Height = height, Width = width };
    grid.Children.Add(ImageChart);

    grid.Measure(new Size(grid.Width, grid.Height));
    grid.Arrange(new Rect(0, 0, grid.Width, grid.Height));
    grid.UpdateLayout();

     /*
     * I cannot find the right combination to get the legend working.
     */
    //ImageChart.InvokeMethodAs(typeof(ChartBase), "LayoutLegends");
    // ImageChart.SetFieldValue("IsUpdateLegend", false);
    ImageChart.InvokeBaseMethod("RenderSeries");
    ImageChart.InvokeBaseMethod("UpdateArea", true);
    //ImageChart.InvokeMethodAs(typeof(ChartBase), "UpdateLegend", ImageChart.Legend, true);
    //ImageChart.InvokeMethodAs(typeof(ChartBase), "UpdateLegendArrangeRect");

    if (text != null)
        grid.Children.Add(new TextBlock()
        {
            Text = text,
            FontFamily = new FontFamily("Arial"),
            FontSize = 17,
            Margin = new Thickness(90, 30, 0, 0)
        });

    RenderTargetBitmap renderBitmap = new RenderTargetBitmap((int)grid.ActualWidth,
                                                             (int)grid.ActualHeight,
                                                             96d,
                                                             96d,
                                                             PixelFormats.Pbgra32);

    renderBitmap.Render(grid);

    var png = new PngBitmapEncoder();
    png.Frames.Add(BitmapFrame.Create(renderBitmap));
    using (Stream stm = File.Create(path))
        png.Save(stm);

    while (ImageChartSeries.Count > 0)
    {
        if (ImageChartSeries[0] != null)
            ImageChartSeries.RemoveAt(0);
    }

    grid.Children.Remove(ImageChart);
    grid = null;
}


Attachment: example_e80d853a.rar


MK Muneesh Kumar G Syncfusion Team November 8, 2018 11:27 AM UTC

Hi Daniel,  
  
Thanks for your update, you can resolve this problem by calling measure for legend as per the below code snippet.  
 
Code snippet 
           ImageChart.InvokeBaseMethod("RenderSeries"); 
 
            if (ImageChart.Legend != null) 
            { 
                (ImageChart.Legend as UIElement).Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); 
            } 
 
            ImageChart.InvokeBaseMethod("UpdateArea", true); 
 
Please verify this solution and let us know whether your issue has been resolved. 
 
Regards, 
Muneesh Kumar G 



DA Daniel November 8, 2018 09:15 PM UTC

Thank you using your code I have resolved the issue and I have made some conclusions.
The correct way to do it is:

var grid = new Grid() { Background = white, Height = height, Width = width };
grid.Children.Add(ImageChart);

grid.Measure(new Size(grid.Width, grid.Height));
grid.Arrange(new Rect(0, 0, grid.Width, grid.Height));

ImageChart.InvokeBaseMethod("RenderSeries");
ImageChart.InvokeBaseMethod("UpdateArea", true);

grid.UpdateLayout();

So the only change I had to make is put the grid's UpdateLayout after the chart rendering. Now the legend displays correctly in the middle of the image as supposed. (not in the middle of the chart area).

However I consider this procedure as a workaround for an existing issue in the SfChart control.
The Save function of the chart should generate the image without any need for margins and without adding extra grid control to cover the transparent areas.
As you can see in the attached files, the Save function does not respect the Margin of the chart and is not extending the white background of the chart around axes. The generated file is having transparent areas around the edges. 

In my opinion, the SfChart control should have some fixed minimum margin that would be respected in normal rendering but also in the Save function (calculating the size of the axis and stretching the image by that amount). Or at least the Margin of the SfChart should be respected by the Save function. Also, I would like to see the Save function having option to export the image at selected height and width but that's only a suggestion.

I would like you to consider these suggestions and I thank you very much for your help.

Attachment: example_17970e47.zip


MK Muneesh Kumar G Syncfusion Team November 9, 2018 04:53 AM UTC

Hi Daniel,  
 
Thanks for the update. Glad that the issue has been resolved. We will consider your suggestion in our SfChart export method improvements. Please let us know if you need any further assistance. 
 
Thanks, 
Muneesh Kumar G. 


Loader.
Live Chat Icon For mobile
Up arrow icon