SciChart® the market leader in Fast WPF Charts, WPF 3D Charts, and now iOS Charting & Android Chart Components

0
0

I’m implementing exporting charts. As part of my export, the user can specify the size of the chart. I’ve managed to get sciChartSurface.ExportToBitmapSource() working fine for single graphs.

However, I’ve got some quite complex layouts, and when I try to render parent controls that contain Scichart controls manually, with say:

ExportUIElement.Measure(size);
ExportUIElement.Arrange(new Rect(size));

 int dpiScaling = 3;
RenderTargetBitmap bmp = new RenderTargetBitmap(Width * dpiScaling, Height * dpiScaling,
                                                                                                           96 * dpiScaling, 96 * dpiScaling,
                                                                                                           PixelFormats.Pbgra32);

I’m finding everything working, the chart layout & axis/labels update and render fine, but the chart content is not re-rendered to the new size, causing some messy/strange visual effects. This one was arranged to a larger size, you can see that the actual chart content is now sitting snugly in the middle of a large margin:

enter image description here

Can I force Scichart to re-render these so I can manually render the component in a different size?

Version
v4.0.3
  • You must to post comments
2
0

Hi there

We have actually implemented ExportToBitmapSource(Size size) with an optional size in v4.2.0 of SciChart or later.

I would strongly recommend using this as the sort of work we have had to do to make SciChart render at a different size has been quite frankly epic!

The reason is, that WPF UIElements are contained in a parent. You cannot change their size, unless you take them out of the parent. If you take them out of the parent temporarily, sometimes, WPF crashes (e.g. when you havea DataTemplate). So the only option is to clone the entire chart, setup a copy in memory, set its size, force layout by calling UpdateLayout(), then render to bitmap.

Which we have done for you in v4.2.0 of SciChart WPF 🙂

Give it a go, let me know the results.

Best regards,
Andrew

  • You must to post comments
1
0

Well, that makes me want to update, much cleaner than setting the Width and Height, exporting to bitmap, then reversing the change.

My issue is the charts are in quite complex arrangements configurable by the user into kind of “reports” so re-building them from individually exported images isn’t easy. For example:

enter image description here

I wonder if I could parse the visual tree, export each chart to an image individually, overlay the image as a WPF component then render the root UI component to bitmap…

  • Andrew
    Those charts look beautiful :) :) Uhm if you want to export these to bitmap at a larger size it makes life really difficult. The parent container constrains the size. You have to pull the chart *out* of its parent to resize it then force update layout.What we did in SciChart v4.2 was implemented ExportToBitmapSource(Size). This actually clones the entire chart and draws a bitmap in memory. We had to work very hard to make this happen. But yes it will only export one chart at a time and you would have to recompose bitmaps. Let me have a think about this I will try to come up with a good solution.
  • Ken Hobbs
    Thanks, really impressed with SciChart so far – it’s taken everything I can throw at it and looks amazing. I’ll experiment with a workaround in the meantime, got some ideas.
  • You must to post comments
0
0

I’ve got a working work-around (and it’s tied in with something I was wanting to implement anyway). I’ve created a new custom control to wrap up the SciChartSurfaces in:

public class ExportableSciChartControl : ContentControl
{
    static ExportableSciChartControl()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(ExportableSciChartControl), new FrameworkPropertyMetadata(typeof(ExportableSciChartControl)));
    }

    public ImageSource ImageForExport
    {
        get { return (ImageSource)GetValue(ImageForExportProperty); }
        set { SetValue(ImageForExportProperty, value); }
    }

    // Using a DependencyProperty as the backing store for ImageForExport.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ImageForExportProperty =
        DependencyProperty.Register("ImageForExport", typeof(ImageSource), typeof(ExportableSciChartControl), new PropertyMetadata(null));

}

With the following template:

<Style TargetType="{x:Type controls:ExportableSciChartControl}">
    <Setter Property="Background" Value="White"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type controls:ExportableSciChartControl}">
                <Border Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding Background}"
                        BorderThickness="{TemplateBinding Background}">
                    <Grid>
                        <ContentPresenter Content="{TemplateBinding Content}"
                                          Margin="{TemplateBinding Padding}"/>
                        <Image Source="{TemplateBinding ImageForExport}"
                               Margin="{TemplateBinding Padding}"/>
                    </Grid>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

You just have to embed any charts as content in one of these like this:

    <controls:ExportableSciChartControl>
        <s:SciChartSurface x:Name="SciChartSurface"
            ...
         </s:SciChartSurface>
    </controls:ExportableSciChartControl>

And export using the handy helper class. The helper class takes the UIElement you want to render off-screen at the size, and propagates through the VisualTree to find all the ExportableSciChartControls (and controls you don’t want to export, like configuration buttons, using a custom attached property), it exports them individually and sets the overlaying image of the ExportableSciChartControls – so when the Render function is called it displays our nicely constructed bitmaps not the charts behind. After the render, we remove the image, make our tool buttons visible again, and everything is happy.

No adding or removing components on the fly:

public class UIChartOffScreenRenderer
{
    public static RenderTargetBitmap RenderOffScreen(FrameworkElement FrameworkElement, double width, double height, int dpi)
    {
        var oldSize = new Size(FrameworkElement.RenderSize.Width, FrameworkElement.RenderSize.Height);
        var size = new Size(width, height);

        ////our first measure and arrange, so we can export the charts in the correct size
        FrameworkElement.Measure(size);
        FrameworkElement.Arrange(new Rect(size));

        //only traverse the tree a single time and return the ExportableSciChartControl, and "HideFromExport" objects (and their origional Visibilities)
        Tuple<List<ExportableSciChartControl>, List<Tuple<FrameworkElement, Visibility>>> searchResults = SearchVisualTree(FrameworkElement);

        double dpiScaling = dpi/96.0;

        //export the charts
        foreach (ExportableSciChartControl exportableSciChartControl in searchResults.Item1)
        {
            SciChartSurface chartSurface = exportableSciChartControl.Content as SciChartSurface;
            if (chartSurface != null)
            {
                //calculate our width and height
                double chartWidth = width * (chartSurface.RenderSize.Width / FrameworkElement.RenderSize.Width);
                double chartHeight = height * (chartSurface.RenderSize.Height / FrameworkElement.RenderSize.Height);
                Size chartSize = new Size(chartWidth * dpiScaling , chartHeight * dpiScaling );

                BitmapSource chartBitmap = chartSurface.ExportToBitmapSource(true, chartSize);

                exportableSciChartControl.ImageForExport = chartBitmap;
            }
        }

        //hide the excluded items
        foreach (Tuple<FrameworkElement, Visibility> hideFromExports in searchResults.Item2)
        {
            hideFromExports.Item1.Visibility = Visibility.Hidden;
        }

        RenderTargetBitmap bmp = new RenderTargetBitmap((int)(width * dpiScaling),
                                                        (int)(height * dpiScaling),
                                                        dpi, dpi,
                                                        PixelFormats.Pbgra32);

        //second measure & arrange to update ready for render
        FrameworkElement.Measure(size);
        FrameworkElement.Arrange(new Rect(size));

        //render our whole control
        bmp.Render(FrameworkElement);

        //reset our ExportControls to remove images
        foreach (ExportableSciChartControl exportableSciChartControl in searchResults.Item1)
        {
            exportableSciChartControl.ImageForExport = null;
        }

        //reset our hidden elements
        foreach (Tuple<FrameworkElement, Visibility> hideFromExports in searchResults.Item2)
        {
            hideFromExports.Item1.Visibility = hideFromExports.Item2;
        }

        //reset the layout
        FrameworkElement.Measure(oldSize);
        FrameworkElement.Arrange(new Rect(oldSize));

        FrameworkElement.InvalidateVisual();

        return bmp;
    }

    private static Tuple<List<ExportableSciChartControl>, List<Tuple<FrameworkElement, Visibility>>> SearchVisualTree(DependencyObject dependencyObject)
    {
        Tuple<List<ExportableSciChartControl>, List<Tuple<FrameworkElement, Visibility>>> results = new Tuple<List<ExportableSciChartControl>, List<Tuple<FrameworkElement, Visibility>>>(new List<ExportableSciChartControl>(), new List<Tuple<FrameworkElement, Visibility>>());

        int childCount = VisualTreeHelper.GetChildrenCount(dependencyObject);
        for (int i = 0; i<childCount; i++)
        {
            DependencyObject child = VisualTreeHelper.GetChild(dependencyObject, i);
            if (child is ExportableSciChartControl)
            {
                results.Item1.Add(child as ExportableSciChartControl);
            }
            else if (child is FrameworkElement)
            {
                bool value = (bool)child.GetValue(ExportHelper.HideFromExportProperty);
                if (value)
                {
                    FrameworkElement childFrameworkElement = child as FrameworkElement;
                    results.Item2.Add(new Tuple<FrameworkElement, Visibility>(childFrameworkElement, childFrameworkElement.Visibility));
                }                  

                Tuple<List<ExportableSciChartControl>, List<Tuple<FrameworkElement, Visibility>>> childReults = SearchVisualTree(child);
                results.Item1.AddRange(childReults.Item1);
                results.Item2.AddRange(childReults.Item2);
            }
        }

        return results;
    }
}

So to render a component containing any number of Charts, all you need is:

RenderTargetBitmap output = UIChartOffScreenRenderer.RenderOffScreen(MyComponent, Width, Height, DPI);

Seems a bit hacky but seems to work well. Some issues, for some reason the background of the chart area is rendered over the whole chart. This does not occur using the (non sized) previous export method:

enter image description here

  • You must to post comments
0
0

I seem to have found an intermittent bug – the size of the export seems to affect the line thickness.

Here’s a screenshot before (top) and after (bottom) exporting:

enter image description here

It appears to be a bit random. Sometimes it doesn’t happen, sometimes it does. Sometimes the line disappears completely. I also once saw it get really thick!

Here it is in action (I reset the series by changing the color):

enter image description here

Should I raise a support request?

  • You must to post comments
0
0

Ah! Found the issue with the first bug (background appearing strange).

I made a small test app, and found that the styles are not carried forward onto the clone. This is the origional chart, old export and new export method (with the size argument) with the style:

        <Style TargetType="s:SciChartSurface">
            <Setter Property="Background" Value="White" />
        </Style>

enter image description here

Most of my styling is done using a custom theme, so it’s only a few things that are not being applied.

I can’t seem to reproduce the line thickness issue. Seems to stack, and is totally random. Here it got thicker a few times in a row!:

enter image description here

  • Andrew
    Hi there, the only way we can help with this is by seeing code samples. Can you share something that reproduces the issue? I opened a support ticket on your behalf from support [at] scichart [dot] com – that is a private ticket – and you can attach code samples there. Only the team and yourself can view them. Thanks!
  • Ken Hobbs
    Ah! I didn’t notice those suggestions from earlier. I could utilize canvases somehow. I’ll send through a simple example of the bug I can reproduce.
  • You must to post comments
0
0

After many (many) contortions, I’ve managed to display SciCharts in FixedDocuments without having to explicitly export them to bitmaps. (Which renders them a bit blurrily).

My method involves two steps: 1) Add a canvas containing a grid containing the SciChart-containing control to the fixed page (the important thing here seems to be the presence of a Panel, the canvas), and 2) When it comes time to print, create a DocumentViewer, place the viewer in the visual tree and place the Fixed Document as the viewer’s Document. Then, for each page in the document containing a SciChart, layout the page again (or for the first time). When printing the entire SciChart will show up, including very crisp labels. Unfortunately the plots themselves, particularly the polar plots, are not terribly crisp. (Skipping this second step, including not placing the DocumentViewer in the visual tree) results in the SciChart title and X and Y Axis labels being printed, but nothing else).

Some code samples showing the steps:

Step 1:

                            var grd = new Grid();
                            var canv = new Canvas();
                            canv.Children.Add(grd);

                            grd.SnapsToDevicePixels = true;
                            grd.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1.0, GridUnitType.Star) });
                            grd.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1.0, GridUnitType.Star) });
                            var tg = new UserControlTextAndGraph();
                            grd.Children.Add(tg);

                            grd.DataContext = viewModelTGTemp;
                            viewModelTGTemp.CommandFullScreen.ExecuteSynchronously(null);
                            var cc = grd;

                            cc.Width = scaledPageWidth;
                            cc.Height = scaledPageHeight;
                            canv.Width = cc.Width;
                            canv.Height= cc.Height;

                            displayable.Width = cc.Width; // Print(FrameworkElement) uses the vm's width and height.
                            displayable.Height = cc.Height;

                            Print(canv);
                            return;

public override void Print(FrameworkElement element)
{
if (element != null)
{
element.SetValue(FixedPage.LeftProperty, CurrentX);
element.SetValue(FixedPage.TopProperty, CurrentY);
CurrentPageFixed.Children.Add(element);
var vm = element.DataContext as IDisplayable;
if (vm != null)
{
element.Width = vm.Width;
element.Height = vm.Height;
}
else
{
var vmRH = element.DataContext as ViewModelReportHeader;
if (vmRH != null)
{
element.Width = CurrentPageFixed.Width;
element.Height = CurrentPageFixed.Height;
}
}
SetDocumentChanged();
CurrentX += element.Width;
}
}

Step 2: Just before printing, massage the Document using LayoutDocument. Once it’s printed, unhook the document viewer.

    private void Layout(FrameworkElement fe, Size sz)
    {
        fe.Dispatcher.Invoke(new Action(() =>
        {
            fe.InvalidateMeasure();
            fe.Measure(sz);
            fe.Arrange(new Rect(0, 0, fe.DesiredSize.Width, fe.DesiredSize.Height));
            fe.UpdateLayout();
        }), System.Windows.Threading.DispatcherPriority.Render);
    }

    private UIElement LayoutDocument()
    {
        var viewer = new DocumentViewer();  // Create the viewer.
        viewer.Width = 1200;  // Leave enough room to be able to display the entire document. My docs are either 916.0 x 1056.0 or
        viewer.Height = 1200; // vice versa 8.5" x 11" in WPF pixels. (96.0 per inch).
        if ((displayingPanel != null)    // Displaying panel is a reference to a canvas in my MVVM View.
            && (displayingPanel.Children != null))
        {
            viewer.Document = Document;     // Add the document to the viewer.
            displayingPanel.Children.Add(viewer);  // add the DocumentViewer to the visual tree.

            foreach (var pageContent in Document.Pages)
            {
                var page = pageContent.Child;
                if (page != null)
                {
                    Layout(page, new Size(page.Width, page.Height)); //  Layout the document page whilst it's in the visual tree.
                }
            }
            Layout(viewer, new Size(1500, 1500));  // probably unnecessary? -  layout the document viewer.
        }
        return (viewer);
    }

Hopefully this will help someone else; any comments on how to render the SciChart curves with higher resolution would be welcome, though I’m not sure that then printing the chart (to PDF, XPS or paper) would be any less blurry.

Attachments
  • Ron Achin
    So… The blurriness issue I was experiencing was not due to SciChart.I’ve switched to DirectX/the High Quality Renderer, and 2) most importantly, turned off JPeg compression in the PDF File printer driver we were using to finally render our output images.The high quality renderer is smooooooth…..
  • Ron Achin
    Here’s an updated version of the LayoutDocument method I used above; It turns out that the code above worked for one or two pages in the document, but if there were more pages, with Annotations, then those annotations were not displayed. The following is better code:private UIElement LayoutDocument() { var viewer = new DocumentViewer(); viewer.Width = 1200; viewer.Height = 1200; Opacity = 0.1; int pageNumber = 0; IsPreviewerEnabled = true; if ((displayingPanel != null) && (displayingPanel.Children != null)) { viewer.Document = Document; displayingPanel.Children.Add(viewer); WaitForApplicationIdle(displayingPanel); foreach (var pageContent in Document.Pages) { pageNumber++; var page = pageContent.Child; if (page != null) { viewer.GoToPage(pageNumber); WaitForApplicationIdle(viewer); Layout(page, new Size(page.Width, page.Height)); WaitForApplicationIdle(viewer); } } Layout(viewer, new Size(2000, 2000)); WaitForApplicationIdle(displayingPanel); } return (viewer); }
  • You must to post comments
Showing 6 results
Your Answer

Please first to submit.