RadCartesianChart ConvertDataToPoint returns incorrect results after axis max/min change

3 posts, 1 answers
  1. Joe
    Joe avatar
    101 posts
    Member since:
    Nov 2017

    Posted 23 Apr 2020 Link to this post

    I have a CartesianCustomAnnotation on my chart that  draws an ellipse.  The ellipse must cover the same amount of chart coordinates (millimeters) in X and Y.  Since ellipse height and width are pixels, I had to bind them with a MultiConverter that uses the chart's ConvertDataToPoint function to convert to pixels   It generally works well.

    But if I change Vertical/Horizontal axes Minimum/Maximum values in code-behind (to achieve Equal-Scale effect I asked you about), even though my binding gets invoked and calls ConvertDataToPoint agin, the pixel values returned do NOT reflect the chart new layout.  I'll get values representing the old axis min/max values and they'll look terrible with the new scaling

    But then, if I do anything at all to force another update of the binding, suddenly, the values returned will look great again.  Even if the change does not alter the underlying values in any meaningful way.

    It's almost as the chart's layout code needs for a UI update happen on the control before ConvertDataToPoint will start returning numbers appropriate to the new axes min/max values.

    Is there any truth to do this?  If I change an axis max/min, do I need to do something (force some layout/update or something) before calling ConvertDataToPoint?    Or is there some chart/axis property I should be binding to instead of what I'm using (see below)

    Note that I've dumped the numbers going into and out of the converter.  They're identical inputs, but completely different outputs from ConvertDataToPoint from when I first change the properties vs when I force a manual update.

    Here is the converter

    public class ChartDataToPixelsConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            if (values == null || values.Length < 2)
                return DependencyProperty.UnsetValue;
     
            if (!(values[0] is RadCartesianChart chart))  // Must have a chart
                return DependencyProperty.UnsetValue;
     
            if (!(values[1] is double length))            // Must have a diameter value in mm
                return DependencyProperty.UnsetValue;
     
            if (!(values[2] is PointElement element))     // Are we returning X pixels or Y pixels?
                element = PointElement.X;
     
            if (element == PointElement.Y)
            {
                var p1 = chart.ConvertDataToPoint(new DataTuple(0, length));
                var p2 = chart.ConvertDataToPoint(new DataTuple(0, 0));
                var result = Math.Abs(p1.Y - p2.Y);
                Debug.WriteLine($"length {length.MM} => yHeight {result}");
                return result;
            }
            else
            {
                var p1 = chart.ConvertDataToPoint(new DataTuple(length, 0));
                var p2 = chart.ConvertDataToPoint(new DataTuple(0, 0));
                var result = Math.Abs(p1.X - p2.X);
                Debug.WriteLine($"length {length.MM} => xWidth {result}");
                return result;
            }
        }
     
        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            return Enumerable.Repeat(Binding.DoNothing, targetTypes?.Length ?? 0).ToArray();
        }
    }

    Here is the the  ContentTemplate where bind the ellipse height and width as I describe.  I've only shown the relevant part of the annotation style

    <Style x:Key="ProfileCircleStyle"TargetType="{x:Type tk:CartesianCustomAnnotation}">
     
        <!-- The visual representation is a GreenYellow ellipse -->
     
        <Setter Property="ContentTemplate">
            <Setter.Value>
                <DataTemplate DataType="{x:Type tk:CartesianCustomAnnotation}">
                    <Ellipse Fill="GreenYellow" d:DataContext="{d:DesignInstance {x:Type gci:IProfileOutputCircle}}">
                        <Ellipse.Width>
                            <!-- Converter only uses first 3 Binding elements.  Others are supplied merely
                            to trigger re-evaluation when the related properties change.  X-axis minimum is always zero -->
                            <MultiBinding Converter="{StaticResource CvtDataToPixels}" Mode="OneWay">
                                <Binding ElementName="MyChart"/>                       <!-- RadCartesianChart -->
                                <Binding Path="Diameter" />                            <!-- Diameter in mm -->
                                <Binding Source="{x:Static gcenum:PointElement.X}"/>   <!-- Return X pixels, not Y -->       
                                <Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type tk:RadCartesianChart}}" Path="Zoom"/>
                                <Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type tk:RadCartesianChart}}" Path="HorizontalAxis.(tk:LinearAxis.Maximum)"/>
                            </MultiBinding>
                        </Ellipse.Width>
                        <Ellipse.Height>
                            <!-- Converter only uses first 3 Binding elements.  Last two are supplied merely
                            to trigger re-evaluation when the related properties change -->
                            <MultiBinding Converter="{StaticResource CvtDataToPixels}" Mode="OneWay">
                                <Binding ElementName="MyChart"/>
                                <Binding Path="Diameter" />
                                <Binding Source="{x:Static gcenum:PointElement.Y}"/>
                        <Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type tk:RadCartesianChart}}" Path="Zoom"/>
                                <Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type tk:RadCartesianChart}}" Path="VerticalAxis.(tk:LinearAxis.Minimum)"/>
                                <Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type tk:RadCartesianChart}}" Path="VerticalAxis.(tk:LinearAxis.Maximum)"/>
                            </MultiBinding>
                        </Ellipse.Height>
                    </Ellipse>
                </DataTemplate>
            </Setter.Value>
        </Setter>
     
    </Style>


    I've attached 3 images to show you what I see.  

    1. The first attachment ("Correct-Normal-Scale.png") shows the chart as initially loaded.  The Ellipse is skewed horizontally because the chart axes are not equal scale.   But I'm using one size for both X and Y

    Here is what my converter dumps for this.  The ellipse is correctly much wider than tall

        length 2.18513189968 => xWidth 516.7183886108953
        length 2.18513189968 => yHeight 48.07290179295998


    2. The second attachment ("Incorrect-Equal-Scale.png") shows how it looks right after I check my "Equal Scale" checkbox at the bottom.  This causes me to manually set the axes min/max values (notice how they've changed).   Even though the X axis has changed dramatically, the ellipse remains skewed horizontally.  It should be a circle.

    Here is what my converter dumps for this change.  Note that X hasn't changed much.  Y has not changed at all

        length 2.18513189968 => xWidth 503.74066305424367
        length 2.18513189968 => yHeight 48.07290179295998

    3. Finally, I manually move the blue region just a tiny bit. This causes the binding to be reevaluated.   The 3rd Attachment ("Correct-Equal-Scale.png") shows how it looks right after.   Notice how the ellipse is now a perfect circle as it should have been. 

    Here is what my converter dumps.  Notice how almost exactly the same input length value now produces identical width and height pixel values.

    length 2.184046088752083 => xWidth 61.02891091119042
    length 2.184046088752083 => yHeight 61.15329048505832

  2. Joe
    Joe avatar
    101 posts
    Member since:
    Nov 2017

    Posted 23 Apr 2020 in reply to Joe Link to this post

    I neglected to post the final image I discussed ("Correct-Equal-Scale.png").  Here it is.
  3. Answer
    Martin Ivanov
    Admin
    Martin Ivanov avatar
    2580 posts

    Posted 28 Apr 2020 Link to this post

    Hello Joe,

    The Conversion API of the chart relies on its size and boundaries and also the current range. It is possible that the chart wasn't fully rendered after the Minimum/Maximum change at the moment when the ConvertDataToPoint method is executed. I've attached a small example showing an alternative approach that you can consider. I hope that helps.

    Regards,
    Martin Ivanov
    Progress Telerik

    Progress is here for your business, like always. Read more about the measures we are taking to ensure business continuity and help fight the COVID-19 pandemic.
    Our thoughts here at Progress are with those affected by the outbreak.
Back to Top