Hide most X-Axis Tick Labels

9 posts, 1 answers
  1. Jonathan
    Jonathan avatar
    37 posts
    Member since:
    Sep 2012

    Posted 18 Dec 2012 Link to this post

    I am trying to figure out how I can control the display of the X-Axis tick labels in the code-behind. 
    I need to be able to create a graph like the sample attached (custom tick labels) where there are many data points for each label but I only want a label to appear aligned with the first of the data points belonging to that label.  Instead I am only able to get what you see in the "too many labels" attached image.

    The labels may not be equal step distances apart as seen in the example going from Dec 14th to Dec 17th as data points are not collected on some days so using dates as my tick values doesn't work well.

    I have used an XCategory item mapping to add the items for the x-axis. 
    Then I added a event that is firing and the label points are being set to blank when I step through in debug. However the chart still displays the tick labels anyway when I view it.   

    Am I making this change in the wrong event or am I changing the wrong property of the tick? What else can I do to make this work?

    UPDATE: Just discovered that if I re-run my code to reload the chart the second run takes a relatively long time (over 60 secs to process) and then the tick labels do hide/show as desired. I'm not sure why the second run is so slow and why my labels won't hide the first time through.

    ItemMapping itemMap = new ItemMapping("StockValue", DataPointMember.YValue);
    seriesMapping.ItemMappings.Add(itemMap);
     
    itemMap = new ItemMapping("XCat", DataPointMember.XCategory);
    seriesMapping.ItemMappings.Add(itemMap);
     
     
    radChart.DataBound += (o, e) =>
    {
        TickPointCollection tickPoints = radChart.DefaultView.ChartArea.AxisX.TickPoints;
        if (tickPoints != null)
        {
            foreach (TickPoint point in tickPoints)
            {
                // don't show a label if it has been marked with an X to hide it
                if (point.Label.Contains("X"))
                {
                    point.Label = " ";  // THIS IS HAPPENING IN DEBUG - so why do they show on the chart?
                }
                else
                {
                    point.Label = point.Label;  // only a few qualify to get here and thus keep their labels
                }
            }
        }
    };
  2. Jonathan
    Jonathan avatar
    37 posts
    Member since:
    Sep 2012

    Posted 20 Dec 2012 Link to this post

    Is there a different event I could be attaching to that would correctly apply the changes since this isn't working for me?
  3. UI for WPF is Visual Studio 2017 Ready
  4. Jonathan
    Jonathan avatar
    37 posts
    Member since:
    Sep 2012

    Posted 20 Dec 2012 Link to this post

    Why don't the changes to the labels take effect for me when I can see the changes made in debugging? The labels still appear unmodified in the displayed chart.

    I have my code that updates the tick labels immediately after setting the datasource and the labels are defined but then the displayed grid doesn't show my changes. I would appreciate some help with this.

    // set the data source with my data
    radChart.ItemsSource = dataList;
     
    // now update the labels for each x-axis tickpoint (should hide most of them but shows a select few)
    TickPointCollection tickPoints = radChart.DefaultView.ChartArea.AxisX.TickPoints;
                if (tickPoints != null)
                {
                    foreach (TickPoint point in tickPoints)
                    {
                        if (point.Label == null) continue;
     
                        if (point.Label.Contains("X"))
                        {
                            point.Label = " ";
                        }
                        else
                        {
                            if (!string.IsNullOrWhiteSpace(point.Label) && point.Label.Contains("~"))
                            {
                                point.Label = point.Label.Substring(0, point.Label.IndexOf('~'));
                            }
                        }
                    }
                }
  5. Nikolay
    Admin
    Nikolay avatar
    385 posts

    Posted 21 Dec 2012 Link to this post

    Hi Jonathan,

    You may try using the LabelStep property to specify that only one of n-number of labels should be displayed. However, in this scenario we would recommend binding your chart to XValue, instead of XCategory and using DateTime values, as categories suggest that each of them is important and cannot be omitted.

    In case the issue persists, it would be very helpful for us if you could open a support ticket and send us a sample application demonstrating this behavior, that we can debug locally and provide further support.

    Regards,
    Nikolay
    the Telerik team

    Explore the entire Telerik portfolio by downloading Telerik DevCraft Ultimate.

  6. Jonathan
    Jonathan avatar
    37 posts
    Member since:
    Sep 2012

    Posted 21 Dec 2012 Link to this post

    Using the LabelStep property I have been able to get labels to show. However while it is working for a particular data sample I'm not sure that it's feasible for all possible data sets I may receive.

    I cannot use DateTime values because my data doesn't provide equal time spaced data points for the grid. I have attached a sample of what happens if I go with the DateTime as an XValue. Because data is only captured for certain hours of each day and weekends no data is added, I get large gaps between data points as seen in the "using dates.jpg" attached image.

    If I switch back to using xCategory then I have to very carefully manufacture my category values to provide unique values for each one and at the same time set the correct display value for the ones that will show based on the LabelStep.  I have been able to pick a step value for the sample data set I have but I cannot guarantee that a nice even step value exists between each set of data points that I want to show a label for.  The attached sample "better - need push labels over.jpg" shows the graph the way I want it to display without the gaps where no data is collected between dates. However I would like to see the labels all pushed to the right to prevent the x and y axis labels from colliding as they do and to have the 'D' in "Dec" line up with the gridline that starts that day's data points. (Actually discovered I can add spaces as padding to the beginning of a label to help with this Eg. "    Dec 11" 

    In another sample - "cut off right side.jpg" - when I save the image (which I need to do to share the graph results with another app) the right hand side is cut off and where the label should say Dec 10 it cuts off part of the 1 at the bottom and the entire 0. This does display properly in my test app if I return the results of the chart to a radchart control in the window of the WPF app so the label is there - just cut off when it is saved as an image. Also this sample again shows the x/y axis labels at the bottom left too close to each other and I would like to have the option to move the first x-axis label over to the right  and/or hide the first x-axis label entirely in some cases.

    All of this to conclude that if I could arbitrarily select which labels I wanted to show or could select the text of the x-axis labels directly for each label, I would have the full customization control I need to 
     - selectively choose which dates to show (even if they aren't an even number of data points/steps apart)
     - not show the first label or last label when we don't want it 
     - choose to show labels for points farther in to avoid being too close to the graph edges to avoid y-axis label collisions or being cut off

    If I could apply the x-axis labels directly for each item then I would simply make the ones I don't want to see to blank or " " and then they would not show. It might be nice if there was a DataPointMember.xAxisLabel type of ItemMapping that would give me this.

    Perhaps you can help me solve the above issues based on my descriptions but I think I will also try to put together a sample app I can send you as you suggested as well to see if I can't achieve the full results I need as I think I should be able to if the code that I had previously posted to change the text of each tick label worked.


  7. Answer
    Nikolay
    Admin
    Nikolay avatar
    385 posts

    Posted 25 Dec 2012 Link to this post

    Hi Jonathan,

    Thank you for the detailed explanation and the screenshots.On to your questions.

    In order to push the X-Axis labels to the right you may either set a few spaces in the label like you have already mentioned , or use a different LayoutMode, so that the first and last labels would be well within the ChartArea.

    There are a couple of work-arounds to handle the last label being clipped - you could set a margin on the ChartArea or rotate the labels at a certain degree.

    Setting manual axis labels could be achieved with the sample code snippet you've sent, however, please note that you'd have to do it in a LayoutUpdated event like this :
    this.radChart.LayoutUpdated += new EventHandler(radChart_LayoutUpdated);
     
    void radChart_LayoutUpdated(object sender, EventArgs e)
            {
                    TickPointCollection tickPoints = radChart.DefaultView.ChartArea.AxisX.TickPoints;
                if (tickPoints != null)
                {
                    foreach (TickPoint point in tickPoints)
                    {
                        if (point.Label == null) continue;
      
                        if (point.Label.Contains("X"))
                        {
                            point.Label = " ";
                        }
                        else
                        {
                            if (!string.IsNullOrWhiteSpace(point.Label) && point.Label.Contains("~"))
                            {
                                point.Label = point.Label.Substring(0, point.Label.IndexOf('~'));
                            }
                        }
                    }
                }
            }

    This is also how you may set empty strings for specific labels, such as the first or the last one if needed, however, a better approach for this would be to use a Style and a Visibility Converter. Here is a sample :
    <UserControl.Resources>
            <local:IndexToCollapsedConverter x:Key="IndexToCollapsedConverter" />
            <Style x:Key="AxisLabelStyle" TargetType="TextBlock">
                <Setter Property="Visibility" Value="{Binding CurrentIndex, Converter={StaticResource IndexToCollapsedConverter}, ConverterParameter=0}" />
            </Style>
        </UserControl.Resources>

    And in code-behind :
    this.radChart.DefaultView.ChartArea.AxisX.AxisStyles.ItemLabelStyle = this.Resources["AxisLabelStyle"] as Style;
    public class IndexToCollapsedConverter : IValueConverter
        {
            #region IValueConverter Members
     
            public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                string stringParameter = "" + parameter;
                int intValue = (int)value;
                if (stringParameter == intValue.ToString())
                    return Visibility.Collapsed;
                else
                    return Visibility.Visible;
            }
     
            public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                throw new NotImplementedException();
            }
     
            #endregion
        }

    Hope this helps.

    Regards,
    Nikolay
    the Telerik team

    Explore the entire Telerik portfolio by downloading Telerik DevCraft Ultimate.

  8. Jonathan
    Jonathan avatar
    37 posts
    Member since:
    Sep 2012

    Posted 07 Jan 2013 Link to this post

    For addressing the the last label getting cut off when saved as an image I have gone with the option of adding a margin to the plotarea. 

    // Color the plot area with a background color
    var plotStyle = new Style(typeof(ClipPanel));
    bbSetter = new Setter(ClipPanel.BackgroundProperty, new SolidColorBrush(Colors.Transparent));
    plotStyle.Setters.Add(bbSetter);
     
    // Add a right side margin to the plotarea
    plotStyle.Setters.Add(new Setter(ClipPanel.MarginProperty, new Thickness(0, 0, 50, 0)));
     
    radChart.DefaultView.ChartArea.PlotAreaStyle = plotStyle;

    In order to completely control the labels I am using both the LayoutUpdated event and the LabelConverter. The event sets the custom label text on each required label and the LabelConverter hides all the other labels that I don't want to see.

    I moved away from the use of the XCategory and am instead using the XValues and assigning incremental values to each data-point. This allows me to draw gridlines accurately and assign the labels accurately as well.

    The following is my code that runs with the LayoutUpdated event. 
    I changed it from what I had started with because it had been taking 30+ seconds to generate the chart the old way. As it is below now it takes only 2 -3 seconds. Instead of iterating through all the tickpoints I now only need to hit the ones that have custom labels that are visible. All the rest are made invisible by the LabelConverter code.
    private void DoLabels(RadChart radChart, Dictionary<double, string> tickPointLabels)
    {
        if (radChart == null) return;
     
        TickPointCollection tickPoints = radChart.DefaultView.ChartArea.AxisX.TickPoints;
        if (tickPoints != null)
        {
            double key = 0;
            foreach (var tickText in tickPointLabels)
            {
                key = tickText.Key;
                tickPoints.First(x => x.Value == key).Label = tickText.Value;
            }
        }
    }

    The following is the LabelConverter code used in the chart:

    // bind to the current index of the label and then hide/show the labels based on the label text using the converter
    Binding styleBinding = new Binding("CurrentIndex") { Converter = new CollapsedLabelConverter() { LabelText = tickLabels, LabelStep = labelStep } };
    xStyle.Setters.Add(new Setter(Control.VisibilityProperty, styleBinding));
     
    radChart.DefaultView.ChartArea.AxisX.AxisStyles.ItemLabelStyle = xStyle;

    And this is the definition of the LabelConverter itself:

    public class CollapsedLabelConverter : IValueConverter
        {
            public Dictionary<double, string> LabelText = new Dictionary<double, string>();
            public int LabelStep = 1;
     
            #region IValueConverter Members
     
            public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                // string strParameter = "" + parameter;
                double key = System.Convert.ToDouble(value) * LabelStep;
     
                if (LabelText.ContainsKey(key) && !string.IsNullOrWhiteSpace(LabelText[key]))
                    return Visibility.Visible;
                else
                    return Visibility.Collapsed;
            }
     
            public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                throw new NotImplementedException();
            }
     
            #endregion
        }

    It uses a Step value because I've found for data sets where I can use a step because the labels are evenly spaced get better performance when setting the xAxis Step value. 

    The end result is that I can now create graphs with custom labels where the text of the labels may not be unique. See attached sample graph image.


    I do wonder if it is possible to create a binding to a converter for the text of the labels. That might be a nice solution that wouldn't require modifying the labels in another event as I have done and I suspect would give better performance since the LabelConverter that hides/shows the labels seems to run very well. I don't have enough WPF experience or knowledge to know how to even try that though. 
  9. Jonathan
    Jonathan avatar
    37 posts
    Member since:
    Sep 2012

    Posted 09 Jan 2013 Link to this post

    I have gone back to using the XCategory and assigning my labels that way as I found using the LayoutUpdated event would add 2 - 3 seconds of processing time to the generation of the charts with about a dozen visible labels or less. The more visible labels the longer it took. By going with the XCategory I don't need the LayoutUpdated event to update the labels and the ValueConverter makes the appropriate labels visible. By making this change now my graphs usually generate in about 1 second or less.

    Actually the LayoutUpdated event seems to be called repeatedly. If you find you want to go that way I suggest the SizeChanged event which for me was fired only once when creating the chart. (I create the chart completely in the code-behind and I save it as an image and I'm done with it.)

  10. Nikolay
    Admin
    Nikolay avatar
    385 posts

    Posted 10 Jan 2013 Link to this post

    Hello Jonathan,

    For performance reasons the better solution in your scenario seems to be the one you've described in your last post - using a ValueConverter for the labels visibility and XCategory, without the need to update them through the LayoutUpdated event, which is indeed called repeatedly.

    Regards,
    Nikolay
    the Telerik team

    Explore the entire Telerik portfolio by downloading Telerik DevCraft Ultimate.

Back to Top
UI for WPF is Visual Studio 2017 Ready