TickPoint Label string is not always being displayed.

5 posts, 0 answers
  1. Francois
    Francois avatar
    4 posts
    Member since:
    Dec 2010

    Posted 19 Feb 2013 Link to this post

    Hi

    Very odd situation. I have a simple line chart (see code below) which has e.g. 100 data points. I'm using XValue on the X axis and doing ranging myself (AutoRange=false). The X values are integers but I manually override the string values for the TickPoints' Label with the actual strings I need to display for each (in general the strings cannot be determined using a format pattern). In the chart's SizeChanged event I recalculate the Step/LabelStep/MinorTickPointMultiplier etc. values each time so that things are spaced nicely (only the Step part of the calculation is shown below).

    Now, if there aren't too many data points then it works perfectly and the Label string values are always shown. 

    But once I exceed say 70 data points, then as I resize the form slowly (to check that it's Step calculations look OK all the way) sometimes the Label values are shown (attached image 1) and sometimes the underlying TickPoint.Value number is shown instead (image 2). It seems at some specific points it flips from the one to the other (repeatably). I checked via a breakpoint and the Labels are always getting set. It is, as if the Tick Points are being reset again afterwards.

    I just cannot figure out what else is happening behind the scenes....

    Here's a small example that demonstrates (just create a WPF form with a chart and attach chart's SizeChanged event)

            private void SetupChart()
            {
                // some data to visualise...
                // NB: need a reasonable number of points, doesn't exhibit at low numbers
                var data = new DataTable();
                data.Columns.Add("x", typeof(int));
                data.Columns.Add("y", typeof(double));
                var date = new DateTime(2012, 1, 1);
                var rnd = new Random();
                for (var i = 0; i < 100; i++)
                {
                    var row = data.NewRow();
                    row["x"] = date.Year * 12 + date.Month - 1;     // integer value calc'd so that 1st of month is evenly spaced (see UpdateLabels() below)
                    row["y"] = 50 + rnd.NextDouble() * 10;
                    data.Rows.Add(row);
                    date = date.AddMonths(1);
                }
     
                // just set up a basic line chart for testing purposes...          
                chart.DefaultView.ChartLegend.Visibility = System.Windows.Visibility.Collapsed;
                chart.DefaultView.ChartArea.EnableAnimations = false;
                chart.DefaultView.ChartArea.AxisY.StripLinesVisibility = System.Windows.Visibility.Collapsed;
                chart.DefaultView.ChartArea.AxisX = new AxisX();
                chart.DefaultView.ChartArea.AxisX.LayoutMode = AxisLayoutMode.Auto;
                chart.DefaultView.ChartArea.AxisX.StripLinesVisibility = System.Windows.Visibility.Collapsed;
                chart.DefaultView.ChartArea.AxisX.AutoRange = false;
                chart.DefaultView.ChartArea.AxisX.MinValue = data.Rows.Cast<DataRow>().Min(r => (int)r["x"]);
                chart.DefaultView.ChartArea.AxisX.MaxValue = data.Rows.Cast<DataRow>().Max(r => (int)r["x"]);
                var mapping = new SeriesMapping();
                mapping.ItemsSource = data;
                mapping.ItemMappings.Add(new ItemMapping("x", DataPointMember.XValue) { FieldType = data.Columns["x"].DataType });
                mapping.ItemMappings.Add(new ItemMapping("y", DataPointMember.YValue) { FieldType = data.Columns["y"].DataType });
                mapping.SeriesDefinition = new LineSeriesDefinition();
                chart.SeriesMappings.Add(mapping);
     
                // to force initial ranging calc to occur...
                chart_SizeChanged(null, null);
            }
     
     
     
            /// <summary>
            /// Event handler
            /// </summary>
            private void chart_SizeChanged(object sender, SizeChangedEventArgs e)
            {
                if (e == null || e.WidthChanged)
                {
                    RecalcRange();
                    UpdateLabels();
                }
            }
     
     
     
            /// <summary>
            /// Event handler
            /// </summary>
            private void updateLabelsButton_Click(object sender, RoutedEventArgs e)
            {
                // manual force recalculate of the labels
                UpdateLabels();
            }
     
     
     
     
            private void RecalcRange()
            {
    #if true
                // pick a reasonable step value...
                var width = chart.DefaultView.ChartArea.ActualWidth;
                var range = chart.DefaultView.ChartArea.AxisX.MaxValue - chart.DefaultView.ChartArea.AxisX.MinValue;
                var step = Math.Max(1, (int)Math.Ceiling(range * chart.DefaultView.ChartArea.AxisX.TicksDistance / width));
                chart.DefaultView.ChartArea.AxisX.Step = step;
    #else
                // it happens for certain constants, such as 8 (but not for 9 etc)...
                chart.DefaultView.ChartArea.AxisX.Step = 8;
    #endif
            }
     
     
     
            private void UpdateLabels()
            {
                foreach (var tickPoint in chart.DefaultView.ChartArea.AxisX.TickPoints)
                {
                    // Reconstitutes the date string for the label from the integer value in x column..
                    var dt = new DateTime((int)tickPoint.Value / 12, ((int)tickPoint.Value % 12) + 1, 1);
                    tickPoint.Label = dt.ToString("MMM-yy");
                }
            }
  2. Petar Kirov
    Admin
    Petar Kirov avatar
    425 posts

    Posted 22 Feb 2013 Link to this post

    Hi Francois,

    I've managed to reproduce the issue, by using your code.

    The problem is that RadChart internally overwrites the TickPoints collection after its size gets changed. To work around this you need to update the labels a bit later. You can do this by attaching to the LayoutUpdate event on size changed:
    private void chart_SizeChanged(object sender, SizeChangedEventArgs e)
    {
        if (e == null || e.WidthChanged)
        {
            RecalcRange();
            //UpdateLabels(); update the labels later
            chart.DefaultView.ChartArea.LayoutUpdated += ChartArea_LayoutUpdated;
        }
    }
     
    void ChartArea_LayoutUpdated(object sender, EventArgs e)
    {
        UpdateLabels();
        //to avoid causing a lauout cycle
        chart.DefaultView.ChartArea.LayoutUpdated -= ChartArea_LayoutUpdated;
    }

    I hope this helps.
     
    All the best,
    Petar Kirov
    the Telerik team

    Explore the entire Telerik portfolio by downloading Telerik DevCraft Ultimate.

  3. UI for WPF is Visual Studio 2017 Ready
  4. Francois
    Francois avatar
    4 posts
    Member since:
    Dec 2010

    Posted 22 Feb 2013 Link to this post

    Hi Petar,

    Thanks, that is a great help.

    I am seeing now, that on re-populate of a complex chart the ChartArea_LayoutUpdated event fires a bit late, so that I'm seeing briefly the numbers appearing before they change to the labels and some chart layout readjustments.

    But I'm thinking, a combined solution (my original plus your suggestion) will probably allow me to produce the correct results in all cases...

    Regards
  5. Francois
    Francois avatar
    4 posts
    Member since:
    Dec 2010

    Posted 22 Feb 2013 Link to this post

    Nope, there's still a nulling of the labels and layout/screen refresh occurring before the effects of the ChartArea_LayoutUpdated come through, which is very apparent on a more complex chart (e.g. 500 points) and doesn't look that great.

    There isn't possibly another event that occurs a bit earlier on to which I could attach? Or is this something that ultimately should be addressed on the Telerik side?

  6. Petar Kirov
    Admin
    Petar Kirov avatar
    425 posts

    Posted 27 Feb 2013 Link to this post

    Hi Francois,

    There are two solutions that I can currently think of.

    1. Do not manually update the labels at all. Instead create a new property on your DataItem which will return the "x" as a DataTime value: 
    public class DataItem
    {
        public double x;
        public double y;
     
        public DateTime Date
        {          
            get
            {              
                return new DateTime((int)x / 12, ((int)x % 12) + 1, 1);
            }
        }
    }
    Then you can specify a LabelFormat like this:
    chart.DefaultView.ChartArea.AxisX.DefaultLabelFormat = "MMM-yy";
    (You can also just return a string in the Date property, but the first approach separates the data from its visual representation.)
    This should also provide a performance improvement, because your not causing unnecessary layout cycles.

    2. The second solution is more of workaround - set an empty label format, so that initially nothing is displayed: 
    chart.DefaultView.ChartArea.AxisX.DefaultLabelFormat = " ";

    Regards,
    Petar Kirov
    the Telerik team

    Explore the entire Telerik portfolio by downloading Telerik DevCraft Ultimate.

Back to Top
UI for WPF is Visual Studio 2017 Ready