c# - GDI+曲线 "overflowing"

标签 c# graphics graph gdi+

我目前正在使用 GDI+ 绘制折线图,​​并使用 Graphics.DrawCurve 来平滑线条。问题是曲线并不总是与我输入的点匹配,这使得曲线在某些点上超出了图形框架,如下所示(红色是 Graphics.DrawLines,绿色是 Graphics.DrawCurve)。

graph

我将如何解决这个问题?

最佳答案

最简单的解决方案是设置张力:

enter image description here

绿色曲线是使用默认张力绘制的,蓝色曲线设置了 0.1f 的张力:

private void panel1_Paint(object sender, PaintEventArgs e)
{
    e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
    e.Graphics.DrawLines(Pens.Red, points.ToArray());
    e.Graphics.DrawCurve(Pens.Green, points.ToArray());
    e.Graphics.DrawCurve(Pens.Blue, points.ToArray(), 0.1f);

}

你需要测试什么是最好的折衷方案,0.2f 还可以,0.3f 已经 overdraw 很多了..

要获得真正好的解决方案,您需要使用 DrawBeziers .这将使您绘制的曲线可以通过这些点而不会过度绘制,并且可以完全控制曲线的半径;但是要做到这一点,您将需要“找到”,即计算 控制点,这绝不是微不足道的..:

enter image description here

这个结果绝不是完美的,但已经足够复杂了。我已经用相同的颜色显示了曲线点和它们各自的控制点。对于每个点,都有一个incoming 和一个outgoing 控制点。对于平滑曲线,它们的曲线点需要具有相同的切线/梯度。

我使用了一些辅助函数来计算关于分割的一些事情:

  • 渐变列表
  • 梯度符号列表
  • 段长度列表
  • 点之间的水平和垂直间隙列表

主函数计算贝塞尔曲线点数组,即曲线点和每对之间的前左next right 控制点

Paint 事件中,它是这样使用的:

List<PointF> bezz = getBezz(points);

using (Pen pen = new Pen(Color.Black, 2f))
       e.Graphics.DrawBeziers(pen, bezz.ToArray());

以下是我使用的函数:

List<float> getGradients(List<PointF> p)
{
    List<float> grads = new List<float>();
    for (int i = 0; i < p.Count - 1; i++)
    {
        float dx = p[i + 1].X - p[i].X;
        float dy = p[i + 1].Y - p[i].Y;
        if (dx == 0) grads.Add(dy == 0 ? 0 : dy > 0 ? 
            float.PositiveInfinity : float.NegativeInfinity);
        else grads.Add(dy / dx);
    }
    return grads;
}

List<float> getLengths(List<PointF> p)
{
    List<float> lengs = new List<float>();
    for (int i = 0; i < p.Count - 1; i++)
    {
        float dx = p[i + 1].X - p[i].X;
        float dy = p[i + 1].Y - p[i].Y;
        lengs.Add((float)Math.Sqrt(dy * dy + dx * dx));
    }
    return lengs;
}

List<float> getGaps(List<PointF> p, bool horizontal)
{
    List<float> gaps = new List<float>();
    for (int i = 0; i < p.Count - 1; i++)
    {
        float dx = p[i + 1].X - p[i].X;
        float dy = p[i + 1].Y - p[i].Y;
        gaps.Add(horizontal ? dx : dy);
    }
    return gaps;
}

List<int> getSigns(List<float> g)
{  
    return g.Select(x => x > 0 ? 1 : x == 0 ? 0 : -1).ToList();  
}

最后是主要功能;在这里我做了一个区分:极值点(最小值和最大值)的控制点应该与点本身的高度相同。这将防止垂直溢出。它们很容易找到:它们的梯度符号总是交替变​​化。

其他点需要对传入和传出控制点具有相同的梯度。我使用分段梯度之间的平均值。 (也许加权平均会更好..)我根据段长度权衡它们的距离..

List<PointF> getBezz(List<PointF> points)
{
    List<PointF> bezz = new List<PointF>();
    int pMax = points.Count;

    List<float> hGaps = getGaps(points, true);
    List<float> vGaps = getGaps(points, false);
    List<float> grads = getGradients(points);
    List<float> lengs = getLengths(points);
    List<int> signs = getSigns(grads);

    PointF[] bezzA = new PointF[pMax * 3 - 2];

    // curve points
    for (int i = 0; i < pMax; i++) bezzA[i * 3] = points[i];

    // left control points
    for (int i = 1; i < pMax; i++)
    {
        float x = points[i].X - hGaps[i - 1] / 2f;
        float y = points[i].Y;
        if (i < pMax - 1 && signs[i - 1] == signs[i])
        {
            float m = (grads[i-1] + grads[i]) / 2f;
            y = points[i].Y - hGaps[i-1] / 2f * m * vGaps[i-1] / lengs[i-1];
        }
        bezzA[i * 3 - 1] = new PointF(x, y);
    }

    // right control points
    for (int i = 0; i < pMax - 1; i++)
    {
        float x = points[i].X + hGaps[i] / 2f;
        float y = points[i].Y;
        if (i > 0 && signs[i-1] == signs[i])
        {
            float m = (grads[i-1] + grads[i]) / 2f;
            y = points[i].Y + hGaps[i] / 2f * m  * vGaps[i] / lengs[i];
        }
        bezzA[i * 3 + 1] = new PointF(x, y);
    }
    return bezzA.ToList();
}

请注意,我没有针对具有相同 x 坐标的点的情况进行编码。所以这对于“功能图”是可以的,但对于,比如数字,比如星星..

关于c# - GDI+曲线 "overflowing",我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30791706/

相关文章:

c# - 在 Postgres 中使用 IEnumerable 参数进行 IN 查询的 Dapper AddDynamicParams

python - 只用python制作的简单游戏

ios - coreplot Xaxis 起始值 iOS 7

c# - 关于线程/后台 worker 的问题

javascript - 带文本输入的 jQuery 模式消息

javascript - 使用两个不同的 yAxis 比例 Highcharts 定义 yAxis 值

opengl - 为什么符号在 opengl 投影矩阵中很重要

javascript - c3.js 中具有多个 XY 折线图的大量或未知数量的数据数组

javascript - 分组图表栏上的 d3 栏标签

c# - 使用过滤器读取/修改 MVC 操作的响应