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