This is a migrated thread and some comments may be shown as answers.

How to improve stacked bar performance?

1 Answer 81 Views
Chart
This is a migrated thread and some comments may be shown as answers.
Doug
Top achievements
Rank 1
Doug asked on 06 Sep 2011, 07:13 PM
I am trying to use the radchart to display data in a stacked bar chart. While I can get the data to display, the performance is terrible when the number of stacked bars is increased. For  example, 20 bins, with 30 stacks takes almost 40 seconds to appears on the screen. The whole time the chart is being updated, the UI is locked/unresponsive. Below is the code in a sample app I constructed to demonstrate the problem. If needed, I can provide a zip of the entire sample project. Are there any tips on how to improve the charting performance given the example code below?

Regards,
Doug

Xaml code

<UserControl x:Class="RadchartSLDemo.MainPage"
    xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"        
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">
 
    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <StackPanel Margin="10">
            <Button Content="(re)load Data" Click="Button_Click" Width="128" Margin="5" Height="64"/>
            <StackPanel Orientation="Horizontal">
            <TextBlock Text="Num bins: "/>
            <TextBox x:Name="Bins" Text="50" Margin="10,0,0,0" />
                <TextBlock Text="Num stacks: " Margin="10,0,0,0" />
                <TextBox x:Name="Stacks" Text="30" Margin="10,0,0,0" />
            </StackPanel>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="Chart Rebind Time:" />
                <TextBlock x:Name="TimeBlock" Text="N/A" Margin="10,0,0,0" />
            </StackPanel>
        </StackPanel>
        <telerik:RadBusyIndicator x:Name="BusyIndicator"  Grid.Row="1">
            <telerik:RadChart x:Name="radChart1" UseDefaultLayout="False" >
                <Grid x:Name="ChartGrid">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*"/>
 
                        <ColumnDefinition Width="Auto"/>
                    </Grid.ColumnDefinitions>
                    <telerik:ChartArea  x:Name="DataChartArea" LegendName="DataChartLegend"
                        Height="Auto" Width="Auto" Grid.Column="0" >
                    </telerik:ChartArea>
 
                    <telerik:ChartLegend Visibility="Collapsed" x:Name="DataChartLegend" Header="Legend:"
                        VerticalAlignment="Top" Height="Auto" Width="Auto" Grid.Column="1">
 
                    </telerik:ChartLegend>
 
                </Grid>
            </telerik:RadChart>
        </telerik:RadBusyIndicator>
    </Grid>
</UserControl>

C# Code-behind

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Telerik.Windows.Controls.Charting;
 
namespace RadchartSLDemo
{
    public partial class MainPage : UserControl
    {
 
        List<List<ChartSampleData>> myTestData = new List<List<ChartSampleData>>();
 
        public MainPage()
        {
            InitializeComponent();
           
            this.Loaded += (s, e) =>
                {
                    DataChartArea.EnableAnimations = false;
 
 
                    //hummm, calling RebindChart() here often causes exception in chart:
  //                  System.NullReferenceException was unhandled by user code
  //Message=Object reference not set to an instance of an object.
  //StackTrace:
  //     at Telerik.Windows.Controls.Charting.SeriesMapping.RebindChart()
  //     at Telerik.Windows.Controls.Charting.SeriesMapping.ManageChangeNotifications(Object originalData, Object oldData)
  //     at Telerik.Windows.Controls.Charting.SeriesMapping.ItemsSourcePropertyChanged(DependencyObject target, DependencyPropertyChangedEventArgs args)
                    //RebindChart();
                };
        }
 
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            BusyIndicator.IsBusy = true;
            BuildData();
            RebindChart();               
            BusyIndicator.IsBusy = false;
                  
        }
 
        protected int GetColor()
        {
 
            int r = ChartSampleData.RandomInt(0, 255);
            int g = ChartSampleData.RandomInt(0, 255);
            int b = ChartSampleData.RandomInt(0, 255);
 
            int rgb = -1;
            rgb = rgb << 8;
            rgb |= r;
            rgb = rgb << 8;
            rgb |= g;
            rgb = rgb << 8;
            rgb |= b;
 
            return rgb;
        }
 
        protected int GetBins()
        {
            int num = 20;
            int.TryParse(Bins.Text, out num);
            return num;
        }
 
        protected int GetStacks()
        {
            int num = 20;
            int.TryParse(Stacks.Text, out num);
            return num;
        }
 
        protected void BuildData()
        {  BusyIndicator.IsBusy = true;
            myTestData.Clear();
            int count = GetBins();
            int stackCount = GetStacks();
 
            for (int i = 0; i < stackCount; i++)
            {
                List<ChartSampleData> data = ChartSampleData.BuildSampleData(GetColor(), count);
                myTestData.Add(data);
            }
 
        }
 
 
        protected void RebindChart()
        {
           
            DateTime st = DateTime.Now;
            ClearAllChartData();
            DateTime clearendtime = DateTime.Now;
            PlotData<StackedBarSeriesDefinition>(DataChartArea);
 
                  
            DateTime et = DateTime.Now;
 
            TimeSpan ctt = clearendtime - st;
            TimeSpan plottt = et - clearendtime;
            TimeSpan tt = et - st;
            TimeBlock.Text = tt.ToString();
          
 
        }
 
        protected void ClearAllChartData()
        {
            ClearPlotData(DataChartArea);        
        }
 
        protected void ClearPlotData(ChartArea chartArea)
        {
            if (chartArea == DataChartArea)
            {
                radChart1.ItemsSource = null;
                radChart1.SeriesMappings.Clear();          
            }
        }
 
        protected void PlotData<TSeriesDefinition>(ChartArea chartArea) where TSeriesDefinition : ISeriesDefinition, new()
        {
            DateTime st = DateTime.Now;
            TimeSpan colorTT = new TimeSpan();
            TimeSpan seriesTT = new TimeSpan();
 
            if (chartArea == null || myTestData == null || myTestData.Count == 0)
                return;
 
 
            try
            {
 
                foreach (var fs in myTestData)
                {
                    List<ChartSampleData> csd = (List<ChartSampleData>)fs;
                    SeriesMapping sm = new SeriesMapping();
                    sm.ChartArea = chartArea;
 
                    var seriesDefintion = new TSeriesDefinition();
                    sm.SeriesDefinition = seriesDefintion;
                    sm.SeriesDefinition.ShowItemLabels = false;
 
                    sm.SeriesDefinition.LegendDisplayMode = LegendDisplayMode.DataPointLabel;
 
 
                    sm.SeriesDefinition.ShowItemToolTips = true;
                    sm.SeriesDefinition.ItemToolTipFormat = "#DATAITEM.Value : #DATAITEM.Count";
                    seriesDefintion.InteractivitySettings =
                        new InteractivitySettings { SelectionMode = ChartSelectionMode.Multiple, SelectionScope = InteractivityScope.Item };
 
 
                    DateTime cot = DateTime.Now;
                    int colorAsInt = csd[0].Color;
                    Color c2 = Color.FromArgb((byte)((colorAsInt >> 0x18) & 0xff),
                              (byte)((colorAsInt >> 0x10) & 0xff),
                              (byte)((colorAsInt >> 8) & 0xff),
                              (byte)(colorAsInt & 0xff));
                    seriesDefintion.Appearance.Fill = new SolidColorBrush(c2);
                    DateTime coet = DateTime.Now;
                    TimeSpan ctt = coet - cot;
                    colorTT += ctt;
 
                    sm.ItemMappings.Add(new ItemMapping("Count", DataPointMember.YValue));
                    sm.ItemMappings.Add(new ItemMapping("Value", DataPointMember.XCategory));
                    sm.ItemMappings.Add(new ItemMapping("Value", DataPointMember.LegendLabel));
 
 
                    DateTime smt = DateTime.Now;
                    sm.ItemsSource = csd;
 
                    radChart1.SeriesMappings.Add(sm);
                    DateTime smet = DateTime.Now;
                    TimeSpan stt = smet - smt;
                    seriesTT += stt;
 
 
                }
 
                chartArea.AxisX.LabelRotationAngle = -45;
            }
            finally
            {
                
            }
            DateTime et = DateTime.Now;
            TimeSpan tt = et - st;
        }
 
 
 
    }
 
    public class ChartSampleData
    {
        private static Random myRandom = new Random((int)DateTime.Now.Ticks);
        public static int RandomInt(int min, int max)
        {
            int num =  myRandom.Next(min, max);
            if (num < 0)
                num = 0;
 
            return num;
        }
 
        public static List<ChartSampleData> BuildSampleData(Color x, int count)
        {
 
 
 
            int rgb = x.A;
            rgb = rgb << 8;
            rgb |= x.R;
            rgb = rgb << 8;
            rgb |= x.G;
            rgb = rgb << 8;
            rgb |= x.B;
 
 
            List<ChartSampleData> list = new List<ChartSampleData>();
            for (int i = 0; i < count; i++)
            {
                ChartSampleData sd = new ChartSampleData();
                string n = "" + i;
                sd.Value = n;
                //try to bias a few 0 counts
                sd.Count = RandomInt(-5,20);
                sd.Color = rgb;
                list.Add(sd);
            }
 
 
            return list;
        }
 
        public static List<ChartSampleData> BuildSampleData(int rgb, int count)
        {
 
            List<ChartSampleData> list = new List<ChartSampleData>();
            for (int i = 0; i < count; i++)
            {
                ChartSampleData sd = new ChartSampleData();
                string n = "" + i;
                sd.Value = n;
                sd.Count = RandomInt(-5, 20);
                sd.Color = rgb;
                list.Add(sd);
            }
 
 
            return list;
        }
 
        public ChartSampleData() { }
 
 
        public string Value
        {
            get;
            set;
        }
 
        public int Count
        {
            get;
            set;
        }
 
        public int Color
        {
            get;
            set;
        }
    }
}

1 Answer, 1 is accepted

Sort by
0
Nikolay
Telerik team
answered on 09 Sep 2011, 09:11 AM
Hello Doug,

The cause for the larger than expected delay in your scenario is because of setting the items source for each mapping, which causes the chart to redraw itself each time you add a new series mapping. A much more efficient approach would be using CollectionIndex and setting the ItemsSource for the whole chart. Please consider the modified PlotData() below : 
 
protected void PlotData<TSeriesDefinition>(ChartArea chartArea) where TSeriesDefinition : ISeriesDefinition, new()
        {
            DateTime st = DateTime.Now;
            TimeSpan colorTT = new TimeSpan();
            TimeSpan seriesTT = new TimeSpan();
  
            if (chartArea == null || myTestData == null || myTestData.Count == 0)
                return;
  
            try
            {
                for (int i = 0; i < myTestData.Count; i++)
                {
                    List<ChartSampleData> csd = (List<ChartSampleData>)myTestData[i];
                    //List<ChartSampleData> csd = (List<ChartSampleData>)fs;
                    SeriesMapping sm = new SeriesMapping();
                    sm.ChartArea = chartArea;
  
                    var seriesDefintion = new TSeriesDefinition();
                    sm.SeriesDefinition = seriesDefintion;
                    sm.SeriesDefinition.ShowItemLabels = false;
                    sm.CollectionIndex = i;
  
                    sm.SeriesDefinition.LegendDisplayMode = LegendDisplayMode.DataPointLabel;
  
                    sm.SeriesDefinition.ShowItemToolTips = true;
                    sm.SeriesDefinition.ItemToolTipFormat = "#DATAITEM.Value : #DATAITEM.Count";
                    seriesDefintion.InteractivitySettings =
                        new InteractivitySettings { SelectionMode = ChartSelectionMode.Multiple, SelectionScope = InteractivityScope.Item };
  
                    DateTime cot = DateTime.Now;
                    int colorAsInt = csd[0].Color;
                    Color c2 = Color.FromArgb((byte)((colorAsInt >> 0x18) & 0xff),
                              (byte)((colorAsInt >> 0x10) & 0xff),
                              (byte)((colorAsInt >> 8) & 0xff),
                              (byte)(colorAsInt & 0xff));
                    seriesDefintion.Appearance.Fill = new SolidColorBrush(c2);
                    DateTime coet = DateTime.Now;
                    TimeSpan ctt = coet - cot;
                    colorTT += ctt;
  
                    sm.ItemMappings.Add(new ItemMapping("Count", DataPointMember.YValue));
                    sm.ItemMappings.Add(new ItemMapping("Value", DataPointMember.XCategory));
                    sm.ItemMappings.Add(new ItemMapping("Value", DataPointMember.LegendLabel));
  
                    DateTime smt = DateTime.Now;
                    //sm.ItemsSource = csd;
  
                    radChart1.SeriesMappings.Add(sm);
                    DateTime smet = DateTime.Now;
                    TimeSpan stt = smet - smt;
                    seriesTT += stt;
                }
                chartArea.AxisX.LabelRotationAngle = -45;
                radChart1.ItemsSource = myTestData;
            }
                  
            finally
            {
            }
            DateTime et = DateTime.Now;
            TimeSpan tt = et - st;
        }
    }

This way we have managed to increase the performance significantly in our local tests ( using the code you've provided ) - to a rebinding time of less than 1 second for a chart with 50 bins and 30 stacks.

Additionally, you may find further information on performance tips in this help topic.

Hope this helps.

Kind regards,
Nikolay
the Telerik team

Thank you for being the most amazing .NET community! Your unfailing support is what helps us charge forward! We'd appreciate your vote for Telerik in this year's DevProConnections Awards. We are competing in mind-blowing 20 categories and every vote counts! VOTE for Telerik NOW >>

Tags
Chart
Asked by
Doug
Top achievements
Rank 1
Answers by
Nikolay
Telerik team
Share this question
or