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

RadCartesianChart ConvertDataToPoint returns incorrect results after axis max/min change

2 Answers 131 Views
ChartView
This is a migrated thread and some comments may be shown as answers.
Joe
Top achievements
Rank 2
Iron
Iron
Veteran
Joe asked on 24 Apr 2020, 04:49 AM

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 Answers, 1 is accepted

Sort by
0
Joe
Top achievements
Rank 2
Iron
Iron
Veteran
answered on 24 Apr 2020, 04:51 AM
I neglected to post the final image I discussed ("Correct-Equal-Scale.png").  Here it is.
0
Accepted
Martin Ivanov
Telerik team
answered on 28 Apr 2020, 05:22 PM

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.
Tags
ChartView
Asked by
Joe
Top achievements
Rank 2
Iron
Iron
Veteran
Answers by
Joe
Top achievements
Rank 2
Iron
Iron
Veteran
Martin Ivanov
Telerik team
Share this question
or