In this example, both axes X and Y use the same scale, it means that the amount of space in the UI is the same for both axes per data unit. We use the ICartesianChartView.MatchAxesScreenDataRatio property, this will set the scale up for us, but we can also build custom scales if necessary.

MatchAxesScreenDataRatio property

When the ICartesianChartView.MatchAxesScreenDataRatio is true, both axes will take the same number of pixels per data unit.

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage x:Class="MauiSample.Axes.MatchScale.View"
             xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:lvc="clr-namespace:LiveChartsCore.SkiaSharpView.Maui;assembly=LiveChartsCore.SkiaSharpView.Maui"
             xmlns:vms="clr-namespace:ViewModelsSamples.Axes.MatchScale;assembly=ViewModelsSamples">
    <ContentPage.BindingContext>
        <vms:ViewModel/>
    </ContentPage.BindingContext>
    <lvc:CartesianChart
        Margin="20"
        Series="{Binding Series}"
        XAxes="{Binding XAxes}"
        YAxes="{Binding YAxes}"
        DrawMarginFrame="{Binding Frame}"
        MatchAxesScreenDataRatio="True"
        ZoomMode="Both"
        TooltipPosition="Hidden">
    </lvc:CartesianChart>
</ContentPage>

using System.Collections.Generic;
using SkiaSharp;
using LiveChartsCore;
using LiveChartsCore.Defaults;
using LiveChartsCore.Kernel.Sketches;
using LiveChartsCore.SkiaSharpView;
using LiveChartsCore.SkiaSharpView.Painting;

namespace ViewModelsSamples.Axes.MatchScale;

// in this example we are going to learn how to match the // mark
// scale of both axes, this means that both X and Y axes will take // mark
// the same amount of space in the screen per unit of data // mark

public class ViewModel
{
    public ISeries[] Series { get; set; } = [
        new LineSeries<ObservablePoint>
        {
            Values = Fetch(),
            Stroke = new SolidColorPaint(new SKColor(33, 150, 243), 4),
            Fill = null,
            GeometrySize = 0
        }
    ];

    // we are forcing the step to be 0.1 // mark
    // but this is just to highlight that both axes use the same scale // mark

    public ICartesianAxis[] XAxes { get; set; } = [
        new Axis
        {
            Name = "X axis",
            SeparatorsPaint = new SolidColorPaint(new SKColor(220, 220, 200)),
            MinStep = 0.1,
            ForceStepToMin = true
        }
    ];

    public ICartesianAxis[] YAxes { get; set; } = [
        new Axis
        {
            Name = "Y axis",
            SeparatorsPaint = new SolidColorPaint(new SKColor(200, 200, 200)),
            MinStep = 0.1,
            ForceStepToMin = true
        }
    ];

    public DrawMarginFrame Frame { get; set; } =
        new() { Stroke = new SolidColorPaint(new SKColor(200, 200, 200), 2) };

    private static List<ObservablePoint> Fetch()
    {
        var list = new List<ObservablePoint>();
        var fx = EasingFunctions.BounceInOut;

        for (var x = 0f; x < 1f; x += 0.001f)
        {
            var y = fx(x);

            list.Add(new()
            {
                X = x - 0.5,
                Y = y - 0.5
            });
        }

        return list;
    }
}

Now both axes use the same scale, we can easily notice this in the grid drawn by the axes separators, this grid is composed by perfect rectangles, no mater if we zoom in/out (or use the panning feature).

sample image

Custom Axis scale

When using the MatchAxesScreenDataRatio, LiveCharts used the MatchAxesScreenDataRatio, this function uses the DrawMarginDefined event to modify the axes range to match the same scale, in the next example we will define our own scale, in this case we want the Y axis to take the double of pixels per unit of data.

using LiveChartsCore;
using LiveChartsCore.Kernel.Sketches;

namespace ViewModelsSamples.Axes.MatchScale;

public static class CustomScaleExtensions
{
    // the DrawMarginDefined event is called once the chart // mark
    // has defined the area where the series will be drawn // mark
    // ignoring the axes, titles, and legends // mark
    // this is where we modify the axes limits to define our custom scale // mark
    public static void DoubleY(this ICartesianChartView chart) =>
        ((CartesianChartEngine)chart.CoreChart).DrawMarginDefined += OnDrawMarginDefined;

    // we are defining the limits of the Y axes to take the double of pixels per unit of data // mark
    private static void OnDrawMarginDefined(CartesianChartEngine chart)
    {
        var x = chart.XAxes[0];
        var y = chart.YAxes[0];

        var xMin = x.MinLimit ?? x.DataBounds.Min;
        var xMax = x.MaxLimit ?? x.DataBounds.Max;

        x.SetLimits(xMin, xMax, notify: false);

        var xScreenDataRatio = chart.DrawMarginSize.Width / (xMax - xMin);

        // with some omitted Algebra, we know that the required range
        // in the Y axis needs to satisfy the next formula:
        // yDelta = height / (xScreenDataRatio * timesScale)

        const int timesScale = 2;
        var yDelta = chart.DrawMarginSize.Height / (xScreenDataRatio * timesScale);
        var midY = GetMidPoint(y);

        // finally we set the limits of the Y axis
        // to +-0.5 * yDelta from the mid point
        y.SetLimits(
            midY - 0.5f * yDelta,
            midY + 0.5f * yDelta,
            notify: false);

        // it is important to use notify: false // mark
        // to avoid the chart to update once we set the limits. // mark
    }

    private static double GetMidPoint(ICartesianAxis axis)
    {
        var min = axis.MinLimit ?? axis.DataBounds.Min;
        var max = axis.MaxLimit ?? axis.DataBounds.Max;
        return (min + max) * 0.5f;
    }
}

Using the previous example, we must remove the MatchAxesScreenDataRatio=True, then we take the chart instance in the UI and call our function to initialize the custom scale:

// where myChart is a reference to chart in the UI.
CustomScaleExtensions.DoubleY(myChart);

Once we run our app again, we can see that our scale works as expected:

sample image

Articles you might also find useful: