Jump to content
Software FX Community

When generating multiple charts, my series collection is being cleared randomly


Mick

Recommended Posts

I created a chart and a button hooked up to an event that would repopulate the chart with random data. The entire chart randomization takes place in the code behind.

Depending on the speed of the machine I was running on and how fast I clicked this button, the series for the chart would occasionally end up empty.

I am not clearing the series manually, so I'm wondering if there's anything going on behind the scenes in the render pass that isn't thread safe (i.e. a thread that clears the series is executing AFTER a thread that sets the data).

I also have a sample project that recreates this that's 26 lines of .xaml and 92 lines of .xaml.cs. Would this be the best place to post it?

 

Link to comment
Share on other sites

The following was built with ChartFX version 8.0.3309.21029.

Here is my sample project. It has 3 buttons and comments throughout the code:

  • The first button generates a new random chart
  • The second button generates data for 1000 new charts, but reuses the same chart control.
  • The third button checks the status of the series for the chart that is currently shown.

To reproduce the error:

  • Click the first button to generate a single chart
  • Click the third button to verify that the chart has 2 series
  • Click the second button to generate 1000 charts worth of data.
    • On my computer, the chart shows the legend but no data
  • Click the third button to verify that the chart has data
    • The chart should have 2 series, but it does not, so I throw an error

Desired behavior: series does not get cleared.

-----

XAML:

-----

<Window
  x:Class="ChartFX.Window1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:cfx="clr-namespace:ChartFX.WPF;assembly=ChartFX.WPF"
  Title="Window1"
  Height="600"
  Width="800"
  >
  <Grid>
  <Grid.ColumnDefinitions>
  <ColumnDefinition />
  <ColumnDefinition />
  <ColumnDefinition />
  </Grid.ColumnDefinitions>
  <Grid.RowDefinitions>
  <RowDefinition Height="19*" />
  <RowDefinition />
  </Grid.RowDefinitions>
  <cfx:Chart x:Name="myChart" Grid.Row="0" Grid.ColumnSpan="3" />
  <Button Grid.Row="1" Content="Generate 1 chart" Margin="2" Click="Button1_Click" />
  <Button Grid.Row="1" Grid.Column="1" Content="Generate 1000 charts" Margin="2" Click="Button2_Click" />
  <Button Grid.Row="1" Grid.Column="2" Margin="2" Content="Verify chart has 2 series" Click="Button3_Click" />
  </Grid>
</Window>

-----

Code:

-----

using System;
using System.Windows;
using ChartFX.WPF;

namespace ChartFX
{
  // Note: I have put locks in my code to make sure that I am not causing the undesired clearing of the series

  public partial class Window1 : Window
  {
  private object LOCKOBJECT = new object();

  public Window1()
  {
  InitializeComponent();
  }

  /// <summary>
  /// Randomize the chart
  /// </summary>
  private void RandomizeChart()
  {
  myChart.Series.Clear();

  label_series_cleared:

  // Create the series and add them to the chart
  SeriesAttributes sa1 = new SeriesAttributes { Gallery = Gallery.Bar };
  SeriesAttributes sa2 = new SeriesAttributes { Gallery = Gallery.Line };
  myChart.Series.Add(sa1);
  myChart.Series.Add(sa2);

  Random random = new Random();

  // Set the data for the two series
  for (int i = 0; i < 10; i++)
  {
  myChart.Data[0, i] = random.Next(1, 25);
  myChart.Data[1, i] = random.Next(1, 25);
  }
  }

  /// <summary>
  /// Check that the chart has series - this should never fail because I only call myChart.Series.Clear() once and immediately repopulate the series (relevant code is locked to be 100% sure)
  /// </summary>
  private void Verify2SeriesExist()
  {
  // Lock this check to make sure that it isn't interfering with our chart generation
  // - i.e. the code at location "label_series_cleared" can NEVER be called until the code at "label_1000_charts" is COMPLETE
  lock (LOCKOBJECT)
  {
  if (myChart.Data.Series == 0)
  {
  // This should never happen, but it does for some reason
  throw new ApplicationException("Series collection has been cleared unexpectedly.");
  }
  }
  }

  /// <summary>
  /// Randomize the chart once - there is USUALLY not a problem here, but myChart.Series is randomly cleared (between 1/10 - 1-100 times in my experience)
  /// </summary>
  private void Button1_Click(object sender, RoutedEventArgs e)
  {
  RandomizeChart();
  }

  /// <summary>
  /// Randomize the chart 1000 times - this reliably creates a problem for me where the series is cleared before the chart renders (creating an empty chart that has legend entries but no visible data)
  /// </summary>
  private void Button2_Click(object sender, RoutedEventArgs e)
  {
  label_1000_charts:

  // Lock the generation of all of the charts
  lock (LOCKOBJECT)
  {
  for (int i = 0; i < 1000; i++)
  RandomizeChart();

  // Verify that we have 2 series immediately after our charts are generated
  Verify2SeriesExist();
  }
  }

  /// <summary>
  /// Check the status of the series collection manually
  /// </summary>
  private void Button3_Click(object sender, RoutedEventArgs e)
  {
  Verify2SeriesExist();
  }
  }
}

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

Thank you,

Mick


AjaxTelerikChartFX.zip

Link to comment
Share on other sites

  • 2 weeks later...

Yes, we were able to reproduce the issue on our latest build, there is a workaround which involves using binding instead of passing data manually, e.g. in RandomizeChart

    // Set the data for the two series

  /*

  for (int i = 0; i < 10; i++) {

  chart1.Data[0, i] = random.Next(1, 25);

  chart1.Data[1, i] = random.Next(1, 25);

  }

  */

 

  List<MyData> data = new List<MyData>();

  for (int i = 0; i < 10; i++) {

  int n0 = random.Next(1, 25);

  int n1 = random.Next(1, 25);

  data.Add(new MyData() { V0 = n0, V1 = n1 });

  }

  sa1.BindingPath = "V0";

  sa2.BindingPath = "V1";

  chart1.ItemsSource = data;

And this class for your data

internal class MyData

{

  public double V0 { get; set; }

  public double V1 { get; set; }

}

 

I am still unsure of what scenario you are trying to test, one thing about this workaround, because of the way we process the data, even though you are setting the ItemsSource collection in a loop that executes one thousand times, we will only process the data once as we delay this for performance reasons

 

JuanC

Link to comment
Share on other sites

Thank you,

I am not actually trying to generate 1000 charts in rapid succession - I only made that example to pinpoint my issue.

I have a screen where I dynamically add/remove charts based on available screen real estate (i.e. "is there room for 1 chart, 2 charts, ... n charts?"). I do this processing in a separate thread that I created - meaning that my thread and the ChartFX rendering thread (or whatever thread was clearing the series in the example) need to talk to each other.

The sample posted is a result of my narrowing down the scope of the issue to the ChartFX render routine (after single-threading all of my code).

Thank you,

Mick

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...