Bug when using long series and sampling

4 posts, 0 answers
  1. Andre
    Andre avatar
    31 posts
    Member since:
    Feb 2011

    Posted 25 Apr 2011 Link to this post

    Hi Telerikers,

    Just found a bug when using long series (I don't know exactly the number, but +1000 is enough) and sampling.
    The scenario is:

    -> AxisX as DateTime 
    -> AxisX using XCategory
    -> Sampling enabled
    -> +1000 datapoints

    Result: the categories in AxisX disappear and only the first one is visible showing 30/12/1899 00:00 (first OADate value). 

    Any workarounds?

    Cheers,

    André
  2. Andre
    Andre avatar
    31 posts
    Member since:
    Feb 2011

    Posted 26 Apr 2011 Link to this post

    Ops, it's the same problem as here http://www.telerik.com/community/forums/silverlight/chart/autorange-doesn-t-works-with-datetime-values-in-axisx-as-xcategory.aspx

    Sorry for the duplicate.

    André

    UPDATE: It's not really a duplicate since I'm not using XValue and XCategory simultaneously.
  3. DevCraft banner
  4. Andre
    Andre avatar
    31 posts
    Member since:
    Feb 2011

    Posted 26 Apr 2011 Link to this post

    Problem solved!

    I created a new dependency property for RadChart where you can apply any kind of filter to the DataSeries after all calculations.

    How it works:

    Create a simple interface called IDataSeriesFilter

            public interface IDataSeriesFilter {
    		void ApplyFilter(DataSeries dataSeries);
    	}

    Then a specialized collection so we can use it in xaml:

    public class DataSeriesFilterCollection : List<IDataSeriesFilter> {}
    

    Extend the RadChart class and add the following code:

    protected override void OnDataBound(ChartDataBoundEventArgs e) {
    			base.OnDataBound(e);
    			ApplyFilters();
    		}
     
    		protected void ApplyFilters() {
    			List<ChartArea> chartAreas = new List<ChartArea>();
    			if(UseDefaultLayout) {
    				chartAreas.Add(DefaultView.ChartArea);
    			}
    			else {
    				chartAreas.AddRange(GetChartAreas());
    			}
    			foreach (IDataSeriesFilter filter in DataSeriesFilters) {
    				foreach (ChartArea chartArea in chartAreas) {
    					foreach (DataSeries serie in chartArea.DataSeries) {
    						filter.ApplyFilter(serie);
    					}	
    				}
    			}
    		}
     
    		protected List<ChartArea> GetChartAreas() {
    			return Descendents(this).OfType<ChartArea>().ToList();
    		}
     
    		public static IEnumerable<DependencyObject> Descendents(DependencyObject root) {
    			int count = VisualTreeHelper.GetChildrenCount(root);
    			for (int i = 0; i < count; i++) {
    				var child = VisualTreeHelper.GetChild(root, i);
    				yield return child;
    				foreach (var descendent in Descendents(child))
    					yield return descendent;
    			}
    		}

    I would love to use the class LogicalTreeExtensions to find all ChartAreas, but Telerik did us the favor to make it internal. Reasons for making an utility class internal are beyond the scope of this post. Also, it would be really good to have a protected method called FindChartAreas that returns all ChartAreas regardless using DefaultLayout or not instead of repeating several times the code that uses LogicalTreeExtensions.

    Ok, now it's very very easy to add modifications to any DataSeries before it gets drawn. Let's show how to create a SamplingFirst filter so we can solve my original problem:

    public class SamplingFirst : IDataSeriesFilter {
     
    		public int SeriesMaxNumberOfDataPoints { getset; }
     
    		public void ApplyFilter(DataSeries dataSeries) {
    			if (SeriesMaxNumberOfDataPoints <= 0) {
    				return;
    			}
    			int grouping = dataSeries.Count / SeriesMaxNumberOfDataPoints * 2;
    			if (grouping < 2return;
    			List<DataPoint> itemsToRemove = new List<DataPoint>();
    			for (int i = 0; i < dataSeries.Count; i++) {
    				if (i % grouping != 0) {
    					itemsToRemove.Add(dataSeries[i]);
    				}
    			}
    			dataSeries.SuspendNotifications();
    			for (int i = 0; i < itemsToRemove.Count; i++) {
    				dataSeries.Remove(itemsToRemove[i]);
    			}
    			dataSeries.ResumeNotifications();
    		}
    	}

    And here the xaml part:

           <TelerikChart:RadChart ItemsSource="{Binding Series}">
     
                <Controls1:RadChart.SamplingSettings>
                    <telerik:SamplingSettings SamplingThreshold="0" />
                </Controls1:RadChart.SamplingSettings>
     
                <TelerikChart:RadChart.DataSeriesFilters>
                    <Filters:SamplingFirst SeriesMaxNumberOfDataPoints="400" />
                </TelerikChart:RadChart.DataSeriesFilters>
            </TelerikChart:RadChart>

    Don't forget to disable SamplingSettings when using your custom one.

    Note: this sampling filter is very simple and it doesn't care which category is removed. It also doesn't generate always 400 datapoints per serie, see the code for info and change it to fit your needs.

    So what we get?
    - It works with XCategories and any number of items (also with zooming)
    - It's very very easy to add new filters, just implement the interface (TDD friendly)
    - You can apply your own logic to category grouping or even change them when it makes sense

    Be careful changing the DataSeries object, you can really mess up your chart. A better approach would be passing an interface contract as the ApplyFilter parameter with only the things you could change in a DataSeries, but I can't change the DataSeries class and I'm too lazy to create a decorator.

    Cheers,

    André Carlucci
     

  5. Tobias Rodewi
    Tobias Rodewi avatar
    24 posts
    Member since:
    Apr 2010

    Posted 10 May 2011 Link to this post

    Thanks for the solution! Works great..
Back to Top