Hi,
I am having a chart in my UI. I am adding a series to the chart and then assign the DataSource to the series. Chart takes lots of time to render the chart.
int[] zi = new int[356]; Infragistics.Silverlight.Chart.Series s3 = new Infragistics.Silverlight.Chart.Series(); s3.ChartType = ChartType.Line; for (int j = 0; j < 356; j++) { zi[j] = j; } igChart3.Series.Add(s3); igChart3.Series[0].DataSource = zi;
Alternatively, if I assign data to the series and then add the series to chart, it works perfectly(much quicker).
int[] zi = new int[356]; Infragistics.Silverlight.Chart.Series s3 = new Infragistics.Silverlight.Chart.Series(); s3.ChartType = ChartType.Line; for (int j = 0; j < 356; j++) { zi[j] = j; } s3.DataSource = zi; igChart1.Series.Add(s3);
Why is this performance difference?
Due to this limitation we are not able to do data binding to the series from XAML code.
Regards,
Madhan
Madhan,
please see the documentation for the RefreshEnabled property of the XamWebChart. The crux of the matter is that if your DataSource implements INotifyCollectionChanged, then everytime you make a change to its contents the chart will rerender to accomodate the change you made. This is a good thing, in general, because you want changes to the data source to be respected in the rendering of the chart.
You don't see the performance problem if you add the data to the collection before adding the series to the chart because the chart has special functionality for when it knows a large amount of data is being added at once, and will disable refreshes while the changes are made. You would see the same functionality if you added all the items to your collection and then made it the data source of the series.
If you want to tell the chart that you are making a lot of changes and you only want it to refresh once, at the end, then you can do this:
igChart3.RefreshEnabled = false; //update the data igChart3.RefreshEnabled = true;
Ah, actually, I just noticed you are using an array, which version of the chart are you using?
-Graham
I've tried to replicate the environment and volume you describe and I have discovered some problems where the chart is refreshing things that it doesn't need to during the binding, which are causing problems when you throw enough volume at them.
In the meantime, if you bind datapoints to the DataPoints collection directly rather than using the DataSource and DataMapping properties, you can avoid these performance problems. I'll attach a sample demonstrating this shortly. Remember to still use the RefreshEnabled property as described above.
I'll also get back to you with a bug number for the performance problem.
I've reported the perfomance issue as bug 30397. You can ask here or Developer Support for the status of this bug at any time. As a workaround you can set the datapoints directly for the series when dealing with these kinds of volumes. Here's a sample that demonstrates one way that you could set the points directly while still using the same kind of binding.
The Xaml:
<UserControl.Resources> <local:DataPointsProxy x:Key="theProxy" LabelPropertyName="Label" ValuePropertyName="Value" /> </UserControl.Resources> <Grid x:Name="LayoutRoot"> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition /> <ColumnDefinition /> <ColumnDefinition /> <ColumnDefinition /> </Grid.ColumnDefinitions> <igChart:XamWebChart x:Name="chart1" Grid.Row="0" Grid.Column="0" RefreshEnabled="{Binding RefreshEnabled, Source={StaticResource theProxy}}"> <igChart:XamWebChart.Series> <igChart:Series ChartType="Line" DataPoints="{Binding DataPoints, Source={StaticResource theProxy}}" /> </igChart:XamWebChart.Series> </igChart:XamWebChart> <igChart:XamWebChart x:Name="chart2" Grid.Row="0" Grid.Column="1" RefreshEnabled="{Binding RefreshEnabled, Source={StaticResource theProxy}}"> <igChart:XamWebChart.Series> <igChart:Series ChartType="Line" DataPoints="{Binding DataPoints, Source={StaticResource theProxy}}" /> </igChart:XamWebChart.Series> </igChart:XamWebChart> <igChart:XamWebChart x:Name="chart3" Grid.Row="0" Grid.Column="2" RefreshEnabled="{Binding RefreshEnabled, Source={StaticResource theProxy}}"> <igChart:XamWebChart.Series> <igChart:Series ChartType="Line" DataPoints="{Binding DataPoints, Source={StaticResource theProxy}}" /> </igChart:XamWebChart.Series> </igChart:XamWebChart> <igChart:XamWebChart x:Name="chart4" Grid.Row="0" Grid.Column="3" RefreshEnabled="{Binding RefreshEnabled, Source={StaticResource theProxy}}"> <igChart:XamWebChart.Series> <igChart:Series ChartType="Line" DataPoints="{Binding DataPoints, Source={StaticResource theProxy}}" /> </igChart:XamWebChart.Series> </igChart:XamWebChart> <igChart:XamWebChart x:Name="chart5" Grid.Row="0" Grid.Column="4" RefreshEnabled="{Binding RefreshEnabled, Source={StaticResource theProxy}}"> <igChart:XamWebChart.Series> <igChart:Series ChartType="Line" DataPoints="{Binding DataPoints, Source={StaticResource theProxy}}" /> </igChart:XamWebChart.Series> </igChart:XamWebChart> <igChart:XamWebChart x:Name="chart6" Grid.Row="1" Grid.Column="0" RefreshEnabled="{Binding RefreshEnabled, Source={StaticResource theProxy}}"> <igChart:XamWebChart.Series> <igChart:Series ChartType="Line" DataPoints="{Binding DataPoints, Source={StaticResource theProxy}}" /> </igChart:XamWebChart.Series> </igChart:XamWebChart> <igChart:XamWebChart x:Name="chart7" Grid.Row="1" Grid.Column="1" RefreshEnabled="{Binding RefreshEnabled, Source={StaticResource theProxy}}"> <igChart:XamWebChart.Series> <igChart:Series ChartType="Line" DataPoints="{Binding DataPoints, Source={StaticResource theProxy}}" /> </igChart:XamWebChart.Series> </igChart:XamWebChart> <igChart:XamWebChart x:Name="chart8" Grid.Row="1" Grid.Column="2" RefreshEnabled="{Binding RefreshEnabled, Source={StaticResource theProxy}}"> <igChart:XamWebChart.Series> <igChart:Series ChartType="Line" DataPoints="{Binding DataPoints, Source={StaticResource theProxy}}" /> </igChart:XamWebChart.Series> </igChart:XamWebChart> <igChart:XamWebChart x:Name="chart9" Grid.Row="1" Grid.Column="3" RefreshEnabled="{Binding RefreshEnabled, Source={StaticResource theProxy}}"> <igChart:XamWebChart.Series> <igChart:Series ChartType="Line" DataPoints="{Binding DataPoints, Source={StaticResource theProxy}}" /> </igChart:XamWebChart.Series> </igChart:XamWebChart> <igChart:XamWebChart x:Name="chart10" Grid.Row="1" Grid.Column="4" RefreshEnabled="{Binding RefreshEnabled, Source={StaticResource theProxy}}"> <igChart:XamWebChart.Series> <igChart:Series ChartType="Line" DataPoints="{Binding DataPoints, Source={StaticResource theProxy}}" /> </igChart:XamWebChart.Series> </igChart:XamWebChart> <Button Grid.Row="2" HorizontalAlignment="Left" Content="Populate" x:Name="populate" Click="populate_Click"/> </Grid>
And the code behind:
public partial class MainPage : UserControl { public MainPage() { InitializeComponent(); proxy = this.Resources["theProxy"] as DataPointsProxy; } private DataPointsProxy proxy; private Random rand = new Random(); public ObservableCollection<TestDataItem> GetTestData() { ObservableCollection<TestDataItem> ret = new ObservableCollection<TestDataItem>(); for (int i = 0; i < 100; i++) { ret.Add(new TestDataItem() { Label = "Label " + i.ToString(), Value = rand.NextDouble() * 100.0 }); } return ret; } private void populate_Click( object sender, RoutedEventArgs e) { proxy.RefreshEnabled = false; proxy.DataSource = GetTestData(); proxy.RefreshEnabled = true; } } public class TestDataItem { public string Label { get; set; } public double Value { get; set; } } public class DataPointsProxy : FrameworkElement { public DataPointsProxy() { DataPoints = new ObservableCollection<DataPoint>(); } public static readonly DependencyProperty RefreshEnabledProperty = DependencyProperty.Register("RefreshEnabled", typeof(bool), typeof(DataPointsProxy), new PropertyMetadata(false)); public bool RefreshEnabled { get { return (bool)this.GetValue( DataPointsProxy.RefreshEnabledProperty); } set { this.SetValue( DataPointsProxy.RefreshEnabledProperty, value); } } public static readonly DependencyProperty DataPointsProperty = DependencyProperty.Register("DataPoints", typeof(ObservableCollection<DataPoint>), typeof(DataPointsProxy), new PropertyMetadata(null)); public ObservableCollection<DataPoint> DataPoints { get { return (ObservableCollection<DataPoint>) this.GetValue( DataPointsProxy.DataPointsProperty); } set { this.SetValue( DataPointsProxy.DataPointsProperty, value); } } public string LabelPropertyName { get; set; } public string ValuePropertyName { get; set; } public static readonly DependencyProperty DataSourceProperty = DependencyProperty.Register("DataSource", typeof(IEnumerable), typeof(DataPointsProxy), new PropertyMetadata(null, (o, e) => { (o as DataPointsProxy).OnDataSourceChanged(e); })); private void UpdateDataPoints() { DataPoints.Clear(); foreach (object item in DataSource) { DataPoint point = new DataPoint(); point.Label = (string)item.GetType() .GetProperty(LabelPropertyName) .GetValue(item, null); point.Value = (double)item.GetType() .GetProperty(ValuePropertyName) .GetValue(item, null); DataPoints.Add(point); } } private void OnDataSourceCollectionChanged( object sender, NotifyCollectionChangedEventArgs e) { UpdateDataPoints(); } private void OnDataSourceChanged( DependencyPropertyChangedEventArgs e) { if (e.OldValue != null) { INotifyCollectionChanged coll = (e.OldValue as INotifyCollectionChanged); if (coll != null) { coll.CollectionChanged -= OnDataSourceCollectionChanged; } } if (e.NewValue != null) { INotifyCollectionChanged coll = (e.NewValue as INotifyCollectionChanged); if (coll != null) { coll.CollectionChanged += OnDataSourceCollectionChanged; } } UpdateDataPoints(); } public IEnumerable DataSource { get { return (IEnumerable)this.GetValue( DataPointsProxy.DataSourceProperty); } set { this.SetValue( DataPointsProxy.DataSourceProperty, value); } } }
Note: you would want to extend this proxy class if you wanted to make efficient updates to the DataSource collectionor detect changes to the objects therein.Let me know if you have any questions.-Graham
Graham,
I am able to see minor performance improvement with the above approach(tried with the same sample code as above). Need to use this approach in my evaluation application and get back to you.
I am facing another issue in evaluation application as well as in sample code above related to memory.
If you keep on clicking populate button for 40 to 50 times over a period of time. it can be in 30 mins or with in possible quickest time. Memory of the IE will keep on increasing. It will reach 1500 MB and finally IE will crash. We are facing the same scenario in our evaluation application and also in the actual application prototype.
Can you check on this behavior?
Could you describe what happens each time you populate the data source? Some pseudocode, or better a sample? Its possible you are leaking event listeners, or the chart may be, as this version does not use a weak event listener pattern. There are definitely some usage scenarios to avoid with this style of control due to the way they need to listen for data updates. But if you can provide some details on your usage we can track down what is occurring.
I have the same problem of memory leak. to reproduce it, it's easy:
Create a simple chart in xaml:
<Grid x:Name="LayoutRoot" Background="White"> <Border Height="Auto" Width="Auto" VerticalAlignment="Top" BorderThickness="1,1,1,1" BorderBrush="White" Margin="0,10,0,0"> <StackPanel Height="Auto" Width="250" Margin="0,0,0,0"> <Canvas x:Name="ChartCanvas0" Width="334" Height="368" HorizontalAlignment="Center">
<Chart:XamWebChart x:Name="ChartControl0" Height="364" Width="366" BorderThickness="0" Canvas.Left="-24" Canvas.Top="5"> <Chart:XamWebChart.Legend> <Chart:Legend Visibility="Collapsed"/> </Chart:XamWebChart.Legend> </Chart:XamWebChart> </Canvas> </StackPanel> </Border> </Grid>
In the "code - behing", create a timer who update the seriescollection each 5s:
private void RefreshData(){ IsRefreshable = false; Curves = GetData(); IsRefreshable = true;}
//it's just an example
private SeriesCollection GetData() { SeriesCollection sColl = new SeriesCollection();
for (int i = 0; i < 7; i++) { Series s = new Series(); s.ChartType = ChartType.ScatterLine; ObservableCollection<DataPoint> lData = new ObservableCollection<DataPoint>(); for (int j = 0; j < 200; j++) { DataPoint d = new DataPoint(); d.ChartParameters.Add(new ChartParameter { Type = ChartParameterType.ValueY, Value = i
}); d.ChartParameters.Add(new ChartParameter { Type = ChartParameterType.ValueX, Value = j
}); lData.Add(d); } s.DataPoints = lData; sColl.Add(s); } return sColl; }
If you check your perfmon or taskmgr, you can see that the memory of the process iexplorer increase for each time you are updating the series of the chart. After some times, an "Outofmemory exception" is throwed by the application.
if you changing the code by just only updated the datapoint or chartparameter, it's the same result.
or, if you binding the seriescollection(MVVM), same result.
The garbageCollector never cleans the old objects.
Have you got an answer for this problem?
Thank you for your quick answer.
Steve,
You may want to try the 10.2 trial. I think you'll find the performance of the new XamDataChart is much better for these kinds of volumes using scatter charts.
Better yet though, with the XamDataChart, you can get even better performance if you use a line series and the CategoryDateTimeXAxis (which supports irregular spacing). The following emulates the situation you describe and returns very speedily. There are some complexities to make the various x axes align, so see my comments interspersed. These concessions aren't needed for the scatter charts, but unfortunately they aren't as zippy as this solution in the current release:
public partial class MainPage : UserControl { List<LineSeries> series = new List<LineSeries>(); public MainPage() { InitializeComponent(); NumericYAxis yAxis = new NumericYAxis(); theChart.Axes.Add(yAxis); List<List<LineData>> datas = new List<List<LineData>>(); for (int k = 0; k < 10; k++) { series.Add(new LineSeries() { //we will plot Double.Nan at the end to //make the axes align, but don't actually show //the value. UnknownValuePlotting = UnknownValuePlotting.DontPlot }); datas.Add(new List<LineData>(30000)); } Random rand = new Random(); DateTime maxX = DateTime.Now.AddYears(-100); List<CategoryDateTimeXAxis> axes = new List<CategoryDateTimeXAxis>(); //generating some sample data. for (int j = 0; j < series.Count; j++) { CategoryDateTimeXAxis xAxis = new CategoryDateTimeXAxis(); xAxis.DateTimeMemberPath = "XValue"; xAxis.Label = "{XVAlue:d}"; axes.Add(xAxis); //need to use a different category xaxis //because the x values are different //but the min/max are aligned //so only need to display one of them if (j != 0) { xAxis.Stroke = new SolidColorBrush(Colors.Transparent); xAxis.LabelSettings = new AxisLabelSettings() { Visibility = Visibility.Collapsed }; } DateTime currentDate = DateTime.Now.AddYears(-100); double currentValue = 100; for (int i = 0; i < 30000; i++) { if (currentDate > maxX) { maxX = currentDate; } datas[j].Add(new LineData() { XValue = currentDate, YValue = currentValue }); if (rand.NextDouble() > .5 ) { currentValue += 5 * rand.NextDouble(); } else { currentValue += -5*rand.NextDouble(); } currentDate = currentDate.AddDays(3 + (-2 + (4 * rand.NextDouble()))); } series[j].XAxis = xAxis; series[j].YAxis = yAxis; series[j].ValueMemberPath = "YValue"; } for (int j = 0; j < series.Count; j++) { //force the x axes to align by plotting an non-plotted //end point for each series at the max x for all series. if (datas[j][datas[j].Count - 1].XValue < maxX) { datas[j].Add(new LineData() { XValue = maxX, YValue = double.NaN }); } series[j].ItemsSource = datas[j]; axes[j].ItemsSource = datas[j]; theChart.Axes.Add(axes[j]); } for (int j = 0; j < series.Count; j++) { theChart.Series.Add(series[j]); } } } public class LineData { public DateTime XValue { get; set; } public double YValue { get; set; } }
Let me know if you have any questions. Hope this helps!-Graham
Hello,
I'm trying to find a silverlight chart that is capable of holding up to 10 time series, each about 30,000 points. I'm plotting them as a scatter plot because the times are irregularly spaced. I just downloaded the trial 2010.1 version and while the chart looks great, it's taking a while to load with just one series of 30,000 points - about 60 secs. I've tried:
1. Create a list of key value pairs and bind that to the series as the data source
2. add the points in a loop one by one as datapoints
3. adjusting the unit of the x axis manually
4. adjusting the label visibility of the x axis manually.
None of these had an impact on the load time.
Is there something I'm missing, or is this the expected performance.
Thanks for any help.
-Steve
I'm going to assume this is the same question as in this post: https://es.infragistics.com/community/forums/f/retired-products-and-controls/41956/xamwebchart-line-chart-tooltipdate-value-and-performance-issue
Please correct me if this is separate.
There are additional XamWebChart performance improvements that should be available in the next services releases and product release. If you could provide me some sort of sample project I could evaluate if these apply for you, or whether we can examine other improvement options.
We were using 9.1 and after shifting to 10.1 we have clear perfomrnace gains, and chart load time has change to 3 to 4 seconds from appox 15 seconds. Is there still any progress in this context?
Thanks
Hi Graham,
We are also facing same issue, can you tell me in which release this solution will be available.
We are doing data point adding as following: where series are of Line Type and has been definded in xaml
this.cht.RefreshEnabled = false;Series s1 = this.chtValuationMuliple.Series[0];Series s2 = this.chtValuationMuliple.Series[1];
DataPoint dp;for (int i = 0; i < MyCount; i++){ dp = new DataPoint(val1, date); s1.DataPoints.Add(dp);
dp = new DataPoint(val2); s2.DataPoints.Add(dp);}
this.cht.RefreshEnabled = true;
My application stucks for about 15 seconds on this.cht.RefreshEnabled = true;
Can you please suggest me some work around?