如何在 SkiaSharp 中将图表坐标转换为设备像素?

How do you translate chart coordinates to device pixels in SkiaSharp?

我正在尝试编写矩阵转换以将图表点转换为 SkiaSharp 中的设备像素。只要我使用 0,0 作为我的最小图表坐标,我就可以使用它,但是如果我需要从负数开始上升,它会导致绘图向左和向下移动。也就是说,X 轴从 window 向左移动,Y 轴从 window.

向下移动

这是一个典型的折线图(左下角的最小图表点,左上角的最小设备点)。我已经在转换中考虑到了这一点。

在逐步执行代码时,我可以看到从 Matrix 返回的坐标不是我期望的坐标,所以我认为问题出在我的转换上,但我无法查明它。

更新: 经过进一步检查,我相信我错了,它没有移动,只是没有正确缩放到屏幕的最大端。图表顶部和右侧的边距比应有的要大,但底部和左侧没问题。我一直无法确定为什么缩放不填充 canvas.

下面是我的矩阵方法:

private SKMatrix ChartToDeviceMatrix, DeviceToChartMatrix;
private void ConfigureTransforms(SKPoint ChartMin, 
    SKPoint ChartMax, SKPoint DeviceMin, SKPoint DeviceMax)
{
    this.ChartToDeviceMatrix = SKMatrix.MakeIdentity();
    float xScale = (DeviceMax.X - DeviceMin.X) / (ChartMax.X - ChartMin.X);
    float yScale = (DeviceMin.Y - DeviceMax.Y) / (ChartMax.Y - ChartMin.Y);

    this.ChartToDeviceMatrix.SetScaleTranslate(xScale, yScale, DeviceMin.X, DeviceMax.Y);
    this.ChartToDeviceMatrix.TryInvert(out this.DeviceToChartMatrix);
}

// Transform a point from chart to device coordinates.
private SKPoint ChartToDevice(SKPoint point)
{
    return this.ChartToDeviceMatrix.MapPoint(point);
}

调用它的代码是:

void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
    SKImageInfo info = args.Info;
    SKSurface surface = args.Surface;
    SKCanvas canvas = surface.Canvas;

    float strokeWidth = 1;
    float margin = 10;

    // SKPaint definitions omitted for brevity.

    var ChartMin = new SKPoint(-10, -1); // Works fine if I change this to 0,0
    var ChartMax = new SKPoint(110, 11);
    var DeviceMin = new SKPoint(margin, margin);
    var DeviceMax = new SKPoint(info.Width - margin, info.Height - margin);
    const float stepX = 10;
    const float stepY = 1;
    const float tickX = 0.5;
    const float tickY = 0.075F;

    // Prepare the transformation matrices.
    this.ConfigureTransforms(ChartMin, ChartMax, DeviceMin, DeviceMax);

    // Draw the X axis.
    var lineStart = new SKPoint(ChartMin.X, 0);
    var lineEnd = new SKPoint(ChartMax.X, 0);
    canvas.DrawLine(this.ChartToDevice(lineStart), this.ChartToDevice(lineEnd), axisPaint);

    // X Axis Tick Marks
    for (float x = stepX; x <= ChartMax.X - stepX; x += stepX)
    {
        var tickMin = new SKPoint(x, -tickY);
        var tickMax = new SKPoint(x, tickY);
        canvas.DrawLine(this.ChartToDevice(tickMin), this.ChartToDevice(tickMax), axisPaint);
    }

    // Draw the Y axis.
    // The inversion of above, basically the same.

我有足够的时间发现自己的问题。我没有正确计算偏移量。 this.ChartToDeviceMatrix.SetScaleTranslate(xScale, yScale, DeviceMin.X, DeviceMax.X);

应该是:

this.ChartToDeviceMatrix.SetScaleTranslate(xScale, yScale, -ChartMin.X * xScale + DeviceMin.Y, -ChartMin.Y * yScale + DeviceMax.Y);

最终矩阵方法是:

private SKMatrix ChartToDeviceMatrix, DeviceToChartMatrix;
private void ConfigureTransforms(SKPoint ChartMin, SKPoint ChartMax, SKPoint DeviceMin, SKPoint DeviceMax)
{
    this.ChartToDeviceMatrix = SKMatrix.MakeIdentity();
    float xScale = (DeviceMax.X - DeviceMin.X) / (ChartMax.X - ChartMin.X);
    float yScale = (DeviceMin.Y - DeviceMax.Y) / (ChartMax.Y - ChartMin.Y);
    float xOffset = -ChartMin.X * xScale + DeviceMin.X;
    float yOffset = -ChartMin.Y * yScale + DeviceMax.Y;

    this.ChartToDeviceMatrix.SetScaleTranslate(xScale, yScale, xOffset, yOffset);
    this.ChartToDeviceMatrix.TryInvert(out this.DeviceToChartMatrix);
}