I am using the WinForms version of LiveCharts to plot a series of data, as a LineSeries, in my .NET application. I would like to be able to adjust the number of 'ticks' shown on the X axis depending on the length of the axis in pixels. So far, I haven't been able to determine what the length of the axis actually is. On inspection at runtime, the Axis
object has a Height
and a Width
of NaN
so my method fails to calculate a correct number of ticks (or steps).
So, my question is, how do I get the correct size of the Axis
object, as shown on screen, in pixels?
My code so far:
Form method calling Chart
class methods to get number of ticks based on a desired pixel spacing and the set the tick spacing on the axis:
private static void UpdateChainageTickSpacing()
{
Axis chainageAxis = Chart.GetAxisByName(AxisOrientation.X, "Chainage");
int numTicks = Chart.GetNumTicksByPixelSpacing(chainageAxis, 40);
Chart.SetTickSpacing(chainageAxis, numTicks, 0);
}
In Chart
class:
public static void SetTickSpacing(Axis axis, int numTicks, int precision)
{
double maxValue = double.MinValue;
double minValue = double.MaxValue;
foreach (Series series in AllSeries)
{
maxValue = Math.Max(GetMaxValue(series, GetAxisOrientation(axis)), maxValue);
minValue = Math.Min(GetMinValue(series, GetAxisOrientation(axis)), minValue);
}
double range = maxValue - minValue;
double spacing = range / numTicks;
spacing = Math.Round(spacing / 10, precision) * 10;
axis.Separator.Step = spacing;
}
public static int GetNumTicksByPixelSpacing(Axis axis, int pixelSpacing)
{
double pixelSize;
if (GetAxisOrientation(axis) == AxisOrientation.X)
{
int index = GetAxisIndexByName(GetAxisCollection(AxisOrientation.X), axis.Name);
pixelSize = cartesianChart.AxisX[index].Width; // this returns NaN
}
else
{
int index = GetAxisIndexByName(GetAxisCollection(AxisOrientation.Y), axis.Name);
pixelSize = cartesianChart.AxisY[index].Height; // this returns NaN
}
return (int)Math.Round(pixelSize / pixelSpacing);
}
The chart has two Canvas
elements: the parent Canvas
contains chart elements like the legends, sections etc. and another child Canvas
as the actual plot area to host the plotted graph.
The axis lengths (x and y) are equal to the plot area's dimensions (width and height).
You can access the plot canvas by referencing the parent Canvas
via the CartesianChart.Content
property:
You need to wait until all elements have been added to the plot canvas to get it's final size.
The chart itself doesn't expose an event that notifies when the content layout has completed. So you have to listen to the UIElement.LayoutUpdated
event of the plot canvas.
To achieve this, you need to subscribe to the Canvas.LayoutUpdated
event from either a Window.ContentRendered
or Window.Loaded
event handler. Since you want to ignore as many redundant layout updates as possible, the Window.ContentRendered
event, which is raised last, is the best in this scenario.
UIElement.LayoutUpdated
is raised during every layout pass like resize or Canvas.Children
collection manipulation like add/move/remove a child element, which is quite often and therefore needs some optimization to reduce redundant re-calculations.
MainWindow.xaml
<Window>
<CartesianChart x:Name="Chart" />
</Window>
MainWindow.xaml.cs
partial class MainWindow: Window
{
public MainWindow()
{
InitializeComponent();
this.ContentRendered += OnContentRendered;
}
private void OnContentRendered(object sender, EventArgs e)
{
this.ContentRendered -= OnContentRendered;
var canvas = this.Chart.Content as Canvas;
var plotCanvas = canvas.Children.OfType<Canvas>().FirstOrDefault();
plotCanvas.LayoutUpdated += GetAxisXLengthOnLayoutUpdated;
}
// Will be called very often
// (on every layout pass of the Canvas sender like on resize or add/move/remove child)
private void GetAxisXLengthOnLayoutUpdated(object sender, EventArgs e)
{
var plotCanvas = sender as Canvas;
// If this is a one time operation, unsubscribe from LayoutUpdated event
plotCanvas.LayoutUpdated -= GetAxisXLengthOnLayoutUpdated;
// The length of the x-axis is equal to the final width of the plot area
var actualAxisXLength = plotCanvas.ActualWidth;
}
}