axis label perfomance with big zoom

14 posts, 0 answers
  1. Vladimir
    Vladimir avatar
    7 posts
    Member since:
    Aug 2016

    Posted 24 Jul Link to this post

    Hi, i'm using LinearAxis for TimeSpan values. Minimum = 1 second and Maximum - maybe 1000 days.

    For values - ScatterLineSeries. In test with perfomance troubles ~500 points.

            <telerik:RadCartesianChart Zoom="10,1"

                Name="_chart" Palette="{StaticResource ScatterCustomPalette}">
                <telerik:RadCartesianChart.HorizontalAxis >
                    <telerik:LinearAxis  IsStepRecalculationOnZoomEnabled="False" LabelFitMode="Rotate">
                        <telerik:LinearAxis.LabelTemplate>
                            <DataTemplate>
                                <TextBlock Text="{Binding Converter={StaticResource LinearAxisTimeSpanConverter}}" />
                            </DataTemplate>
                        </telerik:LinearAxis.LabelTemplate>
                    </telerik:LinearAxis>
                </telerik:RadCartesianChart.HorizontalAxis>

                <telerik:RadCartesianChart.VerticalAxis>
                    <telerik:LinearAxis MajorStep="1"/>
                </telerik:RadCartesianChart.VerticalAxis>

                <telerik:RadCartesianChart.Behaviors>
                    <telerik:ChartPanAndZoomBehavior x:Name="_panZoomBehavior" ZoomMode="Horizontal" DragToZoomThreshold="10" />
                </telerik:RadCartesianChart.Behaviors>
            </telerik:RadCartesianChart>

    Real max zoom depends from data, Axis.Maximum/coef (>1).

    Its wide range for axis X, so i update major step on zoom changing. 

            private void UpdateAxisMajorStep()
            {
                var xAxis = (LinearAxis) _chart.HorizontalAxis;

                double modelWidth = xAxis.Maximum - xAxis.Minimum;
                double viewWidth = xAxis.ActualWidth > 1
                    ? xAxis.ActualVisibleRange.Maximum - xAxis.ActualVisibleRange.Minimum
                    : modelWidth / _chart.Zoom.Width;
                xAxis.MajorStep = viewWidth / 10;
            }

    Behaviour looks good, but when i'm trying zoom in with big factor perfomance slow down.

    Chart shows 10 ticks and labels at horizontal axis, but looks like

        calculated labels count = Chart.ActualRange/MajorStep. 

    For example Maximum = 500000, Visible range 0 - 5000 and MajorStep = 500, count of calculated labels =  1000. Perfomance becomes unusable. 

  2. Martin Ivanov
    Admin
    Martin Ivanov avatar
    1402 posts

    Posted 27 Jul Link to this post

    Hi Vladimir,

    I tried to reproduce the slow performance, but I wasn't able to do it. However, I would suggest you try the axis smart labels mode. The feature tries to automatically calculate a reasonable step in order to avoid labels overlapping. 

    If this doesn't help I would ask you to send the code snippets that reproduces the issue or open a new support ticket from your telerik.com account and attach a runnable project there.

    Regards,
    Martin Ivanov
    Progress Telerik
    Want to extend the target reach of your WPF applications, leveraging iOS, Android, and UWP? Try UI for Xamarin, a suite of polished and feature-rich components for the Xamarin framework, which allow you to write beautiful native mobile apps using a single shared C# codebase.
  3. Vladimir
    Vladimir avatar
    7 posts
    Member since:
    Aug 2016

    Posted 28 Jul in reply to Martin Ivanov Link to this post

    public class PlotTimeSecPnt
    {
        public int Second { get; set; }
        public double Value { get; set; }
     
        public PlotTimeSecPnt(int second, double value)
        {
            Second = second;
            Value = value;
        }
    }
    public class LinearAxisTimeSpanConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            double seconds = System.Convert.ToDouble(value);
            TimeSpan ts = TimeSpan.FromSeconds(seconds);
            return ts.ToString("g");
        }
     
        public object ConvertBack(object value, Type targetType,
            object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

     

    <Window x:Class="LabelsTest.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
            mc:Ignorable="d"
            Title="MainWindow" Height="350" Width="525">
        <Grid>
            <telerik:RadCartesianChart Zoom="10,1"
                Name="_chart" Palette="{StaticResource ScatterCustomPalette}">
                <telerik:RadCartesianChart.HorizontalAxis >
                    <telerik:LinearAxis SmartLabelsMode="None"  IsStepRecalculationOnZoomEnabled="False" LabelFitMode="Rotate">
                        <telerik:LinearAxis.LabelTemplate>
                            <DataTemplate>
                                <TextBlock Text="{Binding Converter={StaticResource LinearAxisTimeSpanConverter}}" />
                            </DataTemplate>
                        </telerik:LinearAxis.LabelTemplate>
                    </telerik:LinearAxis>
                </telerik:RadCartesianChart.HorizontalAxis>

                <telerik:RadCartesianChart.VerticalAxis>
                    <telerik:LinearAxis MajorStep="1"/>
                </telerik:RadCartesianChart.VerticalAxis>

                <telerik:RadCartesianChart.Behaviors>
                    <telerik:ChartTrackBallBehavior SnapMode="ClosestPoint"
                                                    ShowIntersectionPoints="True"
                                                    ShowTrackInfo="True"/>
                    <telerik:ChartPanAndZoomBehavior x:Name="_panZoomBehavior" ZoomMode="Horizontal" DragToZoomThreshold="10" />
                </telerik:RadCartesianChart.Behaviors>
            </telerik:RadCartesianChart>

            <telerik:RadLegend Background="White" 
                               BorderBrush="Black" 
                               BorderThickness="1" 
                               Items="{Binding LegendItems, ElementName=_chart}" 
                               HorizontalAlignment="Right" 
                               VerticalAlignment="Top" />
        </Grid>
    </Window>

     

    public partial class MainWindow
        {
            public MainWindow()
            {
                InitializeComponent();
                DataContext = this;
     
                SetData();
     
                _chart.ZoomChanged += Chart_ZoomChanged;
                _chart.Loaded += _chart_Loaded;
                UpdateAxisMajorStep();
            }
     
            private void _chart_Loaded(object sender, RoutedEventArgs e)
            {
                _chart.PanOffset = new Point(-500, 0);
                _chart.Zoom = new Size(1000, 1);
            }
     
            private void SetData()
            {
                ScatterLineSeries lineSeries = new ScatterLineSeries
                {
                    YValueBinding = new PropertyNameDataPointBinding {PropertyName = "Value"},
                    XValueBinding = new PropertyNameDataPointBinding {PropertyName = "Second"},
                    LegendSettings = new SeriesLegendSettings {Title = "1"}
                };
     
                List<PlotTimeSecPnt> data = new List<PlotTimeSecPnt>();
                Random startShiftRandom = new Random(0);
                Random endShiftRandom = new Random(1);
     
                data.Add(new PlotTimeSecPnt(0, 0));
     
                int x = 0;
                for (int i = 0; i < 500; i++)
                {
                    int shiftStepStart = startShiftRandom.Next(1, 5000);
                    data.Add(new PlotTimeSecPnt(x + shiftStepStart, 0));
                    data.Add(new PlotTimeSecPnt(x + shiftStepStart, 1));
                    x += shiftStepStart;
                    int shiftStepEnd = endShiftRandom.Next(1, 50);
                    data.Add(new PlotTimeSecPnt(x + shiftStepEnd, 1));
                    data.Add(new PlotTimeSecPnt(x + shiftStepEnd, 0));
                    x += shiftStepStart;
                }
     
                lineSeries.ItemsSource = data;
                _chart.Series.Add(lineSeries);
     
     
                LinearAxis hAxis = (LinearAxis) _chart.HorizontalAxis;
     
                hAxis.Minimum = data.Min(r => r.Second);
                hAxis.Maximum = data.Max(r => r.Second);
     
                LinearAxis vAxis = (LinearAxis) _chart.VerticalAxis;
                vAxis.Minimum = 0;
                vAxis.Maximum = 2;
     
                _chart.MaxZoom = new Size(hAxis.Maximum / 100, vAxis.Maximum);
            }
     
            private void Chart_ZoomChanged(object sender, ChartZoomChangedEventArgs e)
            {
                try
                {
                    UpdateAxisMajorStep();
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message);
                }
            }
     
            private void UpdateAxisMajorStep()
            {
                var xAxis = (LinearAxis) _chart.HorizontalAxis;
                if (xAxis.MajorStep > 0)
                {
                    double viewWidth = xAxis.ActualVisibleRange.Maximum - xAxis.ActualVisibleRange.Minimum;
     
                    xAxis.MajorStep = Math.Max(1, viewWidth / 10);
                }
            }
        }

     

    _chart_Loaded - for checking

    Smart labels mode tried, but need more managed behavior

     

     

  4. Vladimir
    Vladimir avatar
    7 posts
    Member since:
    Aug 2016

    Posted 28 Jul in reply to Vladimir Link to this post

    //doesn't matter for results, just logic fix
    int shiftStepEnd = endShiftRandom.Next(1, 50);
    data.Add(new PlotTimeSecPnt(x + shiftStepEnd, 1));
    data.Add(new PlotTimeSecPnt(x + shiftStepEnd, 0));
    x += shiftStepEnd;  // instead of     x += shiftStepStart;
  5. Martin Ivanov
    Admin
    Martin Ivanov avatar
    1402 posts

    Posted 02 Aug Link to this post

    Hello Vladimir,

    Thank you for the attached code. I assembled it into a runnable project and I will check what happens. Will get back to you later with more information on the matter.

    Regards,
    Martin Ivanov
    Progress Telerik
    Want to extend the target reach of your WPF applications, leveraging iOS, Android, and UWP? Try UI for Xamarin, a suite of polished and feature-rich components for the Xamarin framework, which allow you to write beautiful native mobile apps using a single shared C# codebase.
  6. Dinko
    Admin
    Dinko avatar
    414 posts

    Posted 16 Aug Link to this post

    Hello Vladimir,

    Thank you for your patience.

    I have further investigated this behavior on my side and this is expected.  Let me try to explain why. In the provided code snippet you are changing the MajorStep property every time ZoomChanged event is called. When you are setting the major step the chart is recalculating every label in the visible range of the axis. Important here is that the major step is applied to all labels not only on the ones in the visual port. So the reason why the chart hangs is that in your case in the UpdateAxisMajorStep() method the major step is calculated to be a very small number (for example 10) which generate a large number of labels (for example  2000000 / 10). A large number of these labels will be placed into the visible range of the chart. This operation is slow when you have thousands of labels.

    Regards,
    Dinko
    Progress Telerik
    Want to extend the target reach of your WPF applications, leveraging iOS, Android, and UWP? Try UI for Xamarin, a suite of polished and feature-rich components for the Xamarin framework, which allow you to write beautiful native mobile apps using a single shared C# codebase.
  7. Vladimir
    Vladimir avatar
    7 posts
    Member since:
    Aug 2016

    Posted 21 Aug in reply to Dinko Link to this post

    I know...

    A large number of these labels will be placed into the visible range of the chart. This operation is slow when you have thousands of labels.

    So, control currently is not applicable for my requirements and i will continue to use oxyplot without similar problems from box. 

    But for the future i think its not good to generate content, most of which i will not see.

    Maybe it hard to implement now, but in another controls its solved by virtualization or async generation of visible parts or fully managed generation in event handlers.

  8. Martin Ivanov
    Admin
    Martin Ivanov avatar
    1402 posts

    Posted 23 Aug Link to this post

    Hello Vladimir,

    The chart has a built-in UI virtualization. Its axes will generate only the labels that should be plotted into the visible range. The ones outside of this range won't be generated. However, the MajorStep is applied to the whole axis, not only to the visible range. For example, if you have a range between 0 and 100, and you apply a step of 3, the axis will know that it should position a label on each 3rd value (0, 3, 6, 9,..., 99). Anyway, labels will be generated and positioned only in the visible range. So, if the visible range is between 20 and 30, there will be labels generated for values, 21, 24, 27 and 30. 

    In your case at some point the number of the visible labels is 100 and on each MouseMove (which happens in an few milliseconds interval), a new redrawing of those 100 labels is scheduled. This is a bit heavy operation for the framework and it causes the slow performance.

    As an alternative approach you could try to hide the axis labels and use a custom annotations to draw the labels manually. I attached a small example demonstrating this approach. Can you give it a try and let me know if it works for your case?

    Regards,
    Martin Ivanov
    Progress Telerik
    Want to extend the target reach of your WPF applications, leveraging iOS, Android, and UWP? Try UI for Xamarin, a suite of polished and feature-rich components for the Xamarin framework, which allow you to write beautiful native mobile apps using a single shared C# codebase.
  9. Vladimir
    Vladimir avatar
    7 posts
    Member since:
    Aug 2016

    Posted 24 Aug Link to this post

    Simple calculations for 100 labels from attached perfomance tracing screenshot (on top of post):
    (800ms/3300 labels for all range)*100 (target visible label count) = ~ 24ms for 1 measure event (Pan or Zoom change).
    Not perfect method, but its enough speed for most cases.
    For less number of labels it will even faster. In most of cases count of labels < 30.
    But in current render system i need wait 800ms for 1 zoom event, and its not limit. Zoom becomes unusable.

    In your case at some point the number of the visible labels is 100 and on each MouseMove (which happens in an few milliseconds interval), a new redrawing of those 100 labels is scheduled. This is a bit heavy operation for the framework and it causes the slow performance.


    Looks like working manual solution. Not perfect, because i need manually set offset for label area, but far better than drawing text labels manually out of control.
    And i checked this with 100 annotations - it works enough fast in pan and zoom. So, problem not in count of visible labels/annotations. 

    As an alternative approach you could try to hide the axis labels and use a custom annotations to draw the labels manually. I attached a small example demonstrating this approach. Can you give it a try and let me know if it works for your case?
  10. Martin Ivanov
    Admin
    Martin Ivanov avatar
    1402 posts

    Posted 28 Aug Link to this post

    Hello Vladimir,

    I am glad to hear the the annotations approach works for you.

    About the default labels drawing, keep in mind that setting the major step triggers some calculations and processes in the chart which could lead (but not necessarily) to a slower performance for drawing 100 default labels compared to 100 annotations. As for the performance tracing screenshot, it shows ~3300 generated labels and this is the number of the labels in the visible range. On my side I reproduce this if at some point the major step gets too small. The example with the 100 labels in my last reply was merely an example. There are moments that the major step is so small, the number of visible labels could reach thousands. You can avoid this by limiting the MaxZoom. Or by changing the manual MajorStep setting to use some kind of minimum MajorStep if it gets too small.

    As aside note, keep in mind that the MaxZoom property works with relative units. For example 'new Size(10, 0)' setting specifies that the data will be zoomed 10 times according to the horizontal axis and won't be zoomed by the vertical axis.

    Regards,
    Martin Ivanov
    Progress Telerik
    Want to extend the target reach of your WPF applications, leveraging iOS, Android, and UWP? Try UI for Xamarin, a suite of polished and feature-rich components for the Xamarin framework, which allow you to write beautiful native mobile apps using a single shared C# codebase.
  11. Vladimir
    Vladimir avatar
    7 posts
    Member since:
    Aug 2016

    Posted 29 Aug in reply to Martin Ivanov Link to this post

    In the visible range? Do you mean visible range = hAxis.Maximum - hAxis.Minimum?

    As for the performance tracing screenshot, it shows ~3300 generated labels and this is the number of the labels in the visible range.

    Because i about ActualVisibleRange:
                    double viewWidth = xAxis.ActualVisibleRange.Maximum - xAxis.ActualVisibleRange.Minimum;
                    xAxis.MajorStep = Math.Max(1, viewWidth / 10);

    Take a look at new screenshot for code from sample. Its calculated 10000 labes when changed MajorStep.

    It shows only 10 from 10000 labels. Major step is as should be.

    UpdateAxisMajorStep called after zoom changed and ActualVisibleRange is set right already. No reason to calc 10000 labels, except maybe caching for some future actions (pan). 

  12. Martin Ivanov
    Admin
    Martin Ivanov avatar
    1402 posts

    Posted 31 Aug Link to this post

    Hello Vladimir,

    By visible range I mean the axis' ActualVisibleRange.Minimum and ActualVisibleRange.Maximum.

    About the screen shot I am not sure why the calculated labels are 10 thousands but it seems that the MajorStep is too small at some point. Note that the traced information could be gathered after or before setting the major step, so we can't tell what is the step at this specific moment and therefore it could not match the rendered result.

    Regards,
    Martin Ivanov
    Progress Telerik
    Want to extend the target reach of your WPF applications, leveraging iOS, Android, and UWP? Try UI for Xamarin, a suite of polished and feature-rich components for the Xamarin framework, which allow you to write beautiful native mobile apps using a single shared C# codebase.
  13. Vladimir
    Vladimir avatar
    7 posts
    Member since:
    Aug 2016

    Posted 01 Sep in reply to Martin Ivanov Link to this post

    Hello, Martin.

    Maybe MajorStep is too small at some render pipeline point, but not in result.

    In this tracing no changes from code above. And no any user actions in UI, just start App, waiting few seconds for change of MajorStep and drawing lables. Then i click the button to create tracing snapshot. So you can just check it, and should get same results.

    Attached screenshot with only 1 change - row with MajorStep change is commented.

     

     

     

  14. Dinko
    Admin
    Dinko avatar
    414 posts

    Posted 06 Sep Link to this post

    Hi Vladimir,

    Thank you for the provided screenshot. We need more time to check this. We will get back to you as soon as we have more information about your case.

    Regards,
    Dinko
    Progress Telerik
    Want to extend the target reach of your WPF applications, leveraging iOS, Android, and UWP? Try UI for Xamarin, a suite of polished and feature-rich components for the Xamarin framework, which allow you to write beautiful native mobile apps using a single shared C# codebase.
Back to Top