Jump to content
Software FX Community

Chart.Export(...) Function Producing Varying Results


Mick

Recommended Posts

Hello,

I am trying to export chart images from many different data sets in bulk to disk. I noticed that every so often, one of the charts did not render properly.

I think it may have something to do with the ChartFX engine rendering the chart image on a separate thread when there is still layout work that needs to be done by WPF.

Here is a snapshot of one thing that can go "wrong" :

  • Default chart with default data: 
  • The same chart, rendered the exact same way - however, this chart has no reflection
 Posted Image

Other issues that I have experienced in my main project include:

  • labels not being rendered / arranged on pie charts (and when they are rendered, occasionally they are all overlapping in the top left corner of the chart as if they have not been laid out)
  • styles not being applied (despite having a specific style set)
None of these issues is consistent though, so I figured it must be a threading issue. I have tried forcing a call to ((UIElement)chart).Arrange() to force arrangement / layout to take place, but this always work.

Is there any way to force the rendering of the chart to be synchronous so this doesn't happen? Or is there a different way to go about rendering the chart?

I have attached a sample project that demonstrates my issue - sorry in advance for the irrelevance of the second half of the code dealing with md5s, but I wanted to automate the process of "recognizing" differences between charts and it was quick to throw together an example.

Thank you,

Mick


Link to comment
Share on other sites

We do have some special code in Export because WPF needs the visuals to generate an image but if the chart has never been rendered then the visuals do not exist so we do internally force a call Measure/Arrange and optionally have to wait for the visuals to be ready.

An alternative would be to render the charts in a different thread, to use WPF you have to make it STA and ChartFX has some code that checks if the thread is a background thread, e.g.

private void Button_Click (object sender, RoutedEventArgs e)

{

  ThreadStart threadStart = new ThreadStart(this.DoWork);

  Thread thread = new Thread(threadStart);

  thread.SetApartmentState(ApartmentState.STA);

  thread.IsBackground = true;

  thread.Start();

}

private void DoWork()

{

  // Process 10 charts, and put them in this directory:

  int filesToGenerate = 10;

 

Note that when using a separate thread, there will be an exception related to some blurs we use if you press the button twice, it seems that WPF caches some of the bitmap effects but they are not thread safe, you can try setting the Style to ChartFX.WPF.Motifs.Basic.Style, this will obviously make your chart look simpler.

Note that with your original code I only got the reflection issue so I was unable to duplicate the other issues, you can alternatively turn off this reflection by setting UseEffects to false.

Regards,

JuanC

Link to comment
Share on other sites

  • 1 month later...

If you look at the stack trace that gets generated for the exception when you run the code to export a chart twice, you can see that it's because of the the call to BlurBitmapEffect.Radius.

MSDN says static methods of BlurBitmapEffect are thread safe: http://msdn.microsoft.com/en-us/library/system.windows.media.effects.blurbitmapeffect.aspx

This code snippet shows that the threading problem is likely not in the blur, but in the chart:

------------------------------------------

// Create XAML and hook it up to the following:

private void BlurButtonTest_Click(object sender, RoutedEventArgs e)
  {
  // Mock chart with blur
  ThreadStart mockChartThreadStart = new ThreadStart(this.RenderMockChart);
  Thread mockChartThread = new Thread(mockChartThreadStart);
  mockChartThread.SetApartmentState(ApartmentState.STA);
  mockChartThread.IsBackground = true;
  mockChartThread.Start();

  // ChartFX chart
  ThreadStart chartThreadStart = new ThreadStart(this.RenderChart);
  Thread chartExportThread = new Thread(chartThreadStart);
  chartExportThread.SetApartmentState(ApartmentState.STA);
  chartExportThread.IsBackground = true;
  chartExportThread.Start();
  }

  // Create a mock chart and test the blur thread safety
  private void RenderMockChart() { (new MockChart()).TestBlur(); }

  // Render ChartFX
  private void RenderChart() { (new Chart()).Export(FileFormat.Png, System.IO.Path.GetTempFileName()); }

  // Mock chart
  private class MockChart
  {
  // Simulate the blur object that is present on charts
  public BlurBitmapEffect blur = new BlurBitmapEffect();

  // Test the function that ChartFX breaks on
  public void TestBlur() { double radius = blur.Radius; }
  }

------------------------------------------

Press the button twice: 1) The mock class with a BlurBitmapEffect property (accessed in the same way that the chart accesses it) runs fine both times. The chart.Export() function does not.

It seems that you are statically caching a BlurBitmapEffect instance on the Chart class:

  • Thread A creates a chart, and a static BlurBitmapEffect instance is initialized on the Chart class.
  • Thread B tries to create a chart, but cannot access the BlurBitmapEffect instance because it was created on thread A.

This does not appear to be a WPF thread safety issue, but a ChartFX one.

Making a chart's output less visually appealing (by turning off reflections or setting a simple style) doesn't seem like a viable option. Is there some other solution to my original problem?


Thank you,

Mick


Link to comment
Share on other sites

>> This does not appear to be a WPF thread safety issue, but a ChartFX one.

>> It seems that you are statically caching a BlurBitmapEffect instance on the Chart class:

We are not caching a BlurBitmapEffect (at least not intentionally), to test this try the following modifications to your RenderMockChart function

private void RenderMockChart ()

{

  int w = 300;

  int h = 300;

  MockUserControlWithEffect mockControl = new MockUserControlWithEffect();

  mockControl.Measure(new Size(w, h));

  mockControl.Arrange(new Rect(0, 0, w, h));

  RenderTargetBitmap rtb = new RenderTargetBitmap(w, h, 96, 96, PixelFormats.Pbgra32);

  rtb.Render(mockControl);

}

MockUserControlWithEffect.cs

using System;

using System.Windows.Controls;

 

namespace InternalTest

{

  public partial class MockUserControlWithEffect : UserControl

  {

  public MockUserControlWithEffect ()

  {

  InitializeComponent();

  }

  }

}

MockUserControlWithEffect.xaml

<UserControl x:Class="InternalTest.MockUserControlWithEffect"

 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

 Height="120" Width="120">

</UserControl>

App.xaml 

<Style TargetType="{x:Type local:MockUserControlWithEffect}">

  <Setter Property="Template">

  <Setter.Value>

  <ControlTemplate TargetType="{x:Type local:MockUserControlWithEffect}">

  <Grid>

  <Grid.RowDefinitions>

  <RowDefinition Height="20"/>

  <RowDefinition Height="*" />

  </Grid.RowDefinitions>

  <Rectangle Grid.Row="1" Fill="DarkRed">

  <Rectangle.BitmapEffect>

  <BlurBitmapEffect/>

  </Rectangle.BitmapEffect>

  </Rectangle>

  </Grid>

  </ControlTemplate>

  </Setter.Value>

  </Setter>

</Style>

This code throws an exception the second time you hit the button without using any ChartFX code. Interestingly if you set the visuals inside the MockUserControlWithEffect.xaml instead of defining a default style, the exception does not occur so WPF is caching the BlurBitmapEffect when using styles even though all they were supposed to do was clone it when a new MockUserControlWithEffect was instantiated.

Unfortunately ChartFX uses this technique (which I think is the recommended way to create look-less controls).

We just noticed when writing this post that if we use the new Effect API (BitmapEffect was made obsolete in WPF 3.51) then it does not throw an exception

<Rectangle.Effect>

  <BlurEffect/>

</Rectangle.Effect>

So it seems that the WPF bug is related to BitmapEffects in particular. Even though it would be a very simple change for us to make, we have tried to make sure we do not force a specific framework version to our users. We will discuss this internally as AFAIK even though ChartFX for WPF works with .NET 3.0, at design time we are already forced by a VS 2008 bug to require 3.5 SP1.

Feedback as always, would be greatly appreciated.

Note that the reflection at the bottom uses a different technique so I am still not sure if you would get the reflection consistently when exporting the chart on a background thread.

JuanC

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...