【问题标题】:Drawing Curvy Lines Through PDFBox通过 PDFBox 绘制曲线
【发布时间】:2020-11-25 00:02:27
【问题描述】:

使用 PDFBox,我创建了一个折线图来绘制一些数据,它看起来很像您通过谷歌搜索看到的任何一般折线图。它看起来也与我附加到这个问题的折线图相同。折线图绘制算法的工作方式是先查看当前点,然后查看下一个点,如果在那里找到有效点,则绘制一条线。

我的问题是客户不喜欢这些线彼此连接的清晰程度。相反,他们希望线条之间的连接以更多弯曲的方式发生。附件是一张关于客户想要什么的粗略想法的图片。请注意,虽然线条看起来非常弯曲,但客户特别关心线条连接本身是否弯曲,而不是像标准折线图中那样锐利。

到目前为止,我已经尝试使用 Bézier 曲线,但我似乎无法找到正确的值来使其适用于点之间的所有不同幅度。我首先尝试更改线帽和线连接样式,但这并没有在线连接之间产生所需的“cuvyness”。我也考虑过使用路径来实现这个结果,但我还没有弄清楚如何继续。

我是否遗漏了一些可以使绘制这些线条更容易的东西?如果没有,谁能帮我找出正确的贝塞尔值/路径来实现这些曲线?提前感谢您提供任何建议/代码示例。

由于保密协议,我无法给出显示图表是如何绘制和绘制的代码示例(这将完全放弃我们的算法)。我只能说,我为数据应该如何绘制在图表中创建了一个内部表示,并且这个系统在提供的图像中非常粗略地翻译。我可以说绘制数据的函数专门使用 PDPageContentStream 类的 lineTo 和 strokeTo 函数,在初始 moveTo 到基于我们内部坐标表示的起点位置之后。

A rough idea of the curves the client wants.

【问题讨论】:

  • 您使用虚拟数据完成的示例图表的正确minimal reproducible example 将帮助我们开始编写代码。请提供一个
  • 由于保密协议,我不能这样做。无论如何,绘制图表的移动部件也太多了。我需要知道的是如何弯曲线条,我认为可以在绘制数据的图表的上下文之外完成。我认为提供的图像为图表提供了良好的虚拟数据上下文。
  • 我不是要您的实际代码,而是要隔离问题的全新代码。一个使用虚拟数据创建图表的程序(随机数很好)。很少有人会在没有你的代码的情况下尝试测试一些东西
  • 我更新了问题。绘图仅使用 PDPageContentStream 类的 lineTo 和 strokeTo 函数完成。这就是连接看起来很锋利的原因。我需要知道是否有办法以更曲线的方式加入它们。
  • 如果您只使用 moveTolineTo,它显然不会变得弯曲(除非您以微步进行)。您确实应该使用贝塞尔曲线,至少在连接附近。如需更多帮助,帮助者需要更多信息。

标签: java pdf pdfbox


【解决方案1】:

---一个快速的“解决方案”是使用圆线连接而不是斜接连接(默认)---似乎我错过了这个。

您的示例中的图表可能使用了曲线插值,这个问题和答案可能会对您有所帮助:How does polyline simplification in Adobe Illustrator work?

下面的代码显示了如何将线列表转换为贝塞尔连接线(它是 C#,但可以通过最小的更改转换为 Java):

/// <summary>
/// Draws the Bezier connected lines on the page.
/// </summary>
/// <param name="page">Page where to draw the lines.</param>
/// <param name="points">List of points representing the connected lines.</param>
/// <param name="pen">Pen to draw the final path.</param>
/// <param name="smoothFactor">Smooth factor for computing the Bezier curve</param>
/// <param name="font"></param>
private static void DrawBezierConnectedLines(PDFPage page, PDFPoint[] points, PDFPen pen, double smoothFactor, PDFFont font)
{

    PDFPath path = new PDFPath();
    path.StartSubpath(points[0].X, points[0].Y);

    for (int i = 0; i < points.Length - 2; i++)
    {
        PDFPoint[] pts = ComputeBezierConnectedLines(points[i], points[i + 1], points[i + 2], smoothFactor, i == 0, i == points.Length - 3);
        switch (pts.Length)
        {
            case 2: // Intermediate/last section - straight lines
                path.AddLineTo(pts[0].X, pts[0].Y);
                path.AddLineTo(pts[1].X, pts[1].Y);
                break;
            case 3: // First section - straight lines
                path.AddLineTo(pts[0].X, pts[0].Y);
                path.AddLineTo(pts[1].X, pts[1].Y);
                path.AddLineTo(pts[2].X, pts[2].Y);
                break;
            case 4: // Intermediate/last section
                path.AddLineTo(pts[0].X, pts[0].Y);
                path.AddBezierTo(pts[1].X, pts[1].Y, pts[1].X, pts[1].Y, pts[2].X, pts[2].Y);
                path.AddLineTo(pts[3].X, pts[3].Y);
                break;
            case 5: // First section
                path.AddLineTo(pts[0].X, pts[0].Y);
                path.AddLineTo(pts[1].X, pts[1].Y);
                path.AddBezierTo(pts[2].X, pts[2].Y, pts[2].X, pts[2].Y, pts[3].X, pts[3].Y);
                path.AddLineTo(pts[4].X, pts[4].Y);
                break;
        }
    }

    page.Canvas.DrawPath(pen, path);

    page.Canvas.DrawString($"Smooth factor = {smoothFactor}", font, new PDFBrush(), points[points.Length - 1].X, points[0].Y);
}

/// <summary>
/// Given a sequence of 3 consecutive points representing 2 connected lines the method computes the points required to display the new lines and the connecting curve.
/// </summary>
/// <param name="pt1">First point</param>
/// <param name="pt2">Second point</param>
/// <param name="pt3">Third point</param>
/// <param name="smoothFactor">Smooth factor for computing the Bezier curve</param>
/// <param name="isFirstSection">True if the points are the first 3 in the list of points</param>
/// <param name="isLastSection">True if the 3 points are last 3 in the list of points.</param>
/// <returns>A list of points representing the new lines and the connecting curve.</returns>
/// <remarks>The method returns 5 points if this is the first section, points that represent the first line, connecting curve and last line.
/// If this is not the first section the method returns 4 points representing the connecting curve and the last line.</remarks>
private static PDFPoint[] ComputeBezierConnectedLines(PDFPoint pt1, PDFPoint pt2, PDFPoint pt3, double smoothFactor, bool isFirstSection, bool isLastSection)
{
    PDFPoint[] outputPoints = null;

    if (smoothFactor > 0.5)
    {
        smoothFactor = 0.5; // Half line maximum
    }
    if (((pt1.X == pt2.X) && (pt2.X == pt3.X)) || // Vertical lines
        ((pt1.Y == pt2.Y) && (pt2.Y == pt3.Y)) || // Horizontal lines
        (smoothFactor == 0))
    {
        if (!isFirstSection)
        {
            pt1 = ComputeIntermediatePoint(pt1, pt2, smoothFactor, false);
        }
        if (!isLastSection)
        {
            pt3 = ComputeIntermediatePoint(pt2, pt3, smoothFactor, true);
        }
        if (isFirstSection)
        {
            outputPoints = new PDFPoint[] { pt1, pt2, pt3 };
        }
        else
        {
            outputPoints = new PDFPoint[] { pt2, pt3 };
        }
    }
    else
    {
        PDFPoint startPoint = new PDFPoint(pt1);
        if (!isFirstSection)
        {
            startPoint = ComputeIntermediatePoint(pt1, pt2, smoothFactor, false);
        }
        PDFPoint firstIntermediaryPoint = ComputeIntermediatePoint(pt1, pt2, smoothFactor, true);
        PDFPoint secondIntermediaryPoint = new PDFPoint(pt2);
        PDFPoint thirdIntermediaryPoint = ComputeIntermediatePoint(pt2, pt3, smoothFactor, false);
        PDFPoint endPoint = new PDFPoint(pt3);
        if (!isLastSection)
        {
            endPoint = ComputeIntermediatePoint(pt2, pt3, smoothFactor, true);
        }

        if (isFirstSection)
        {
            outputPoints = new PDFPoint[] { startPoint, firstIntermediaryPoint, secondIntermediaryPoint, thirdIntermediaryPoint, endPoint };
        }
        else
        {
            outputPoints = new PDFPoint[] { firstIntermediaryPoint, secondIntermediaryPoint, thirdIntermediaryPoint, endPoint };
        }
    }

    return outputPoints;
}

/// <summary>
/// Given the line from pt1 to pt2 the method computes an intermediary point on the line.
/// </summary>
/// <param name="pt1">Start point</param>
/// <param name="pt2">End point</param>
/// <param name="smoothFactor">Smooth factor specifying how from from the line end the intermediary point is located.</param>
/// <param name="isEndLocation">True if the intermediary point should be computed relative to end point, 
/// false if the intermediary point should be computed relative to start point.</param>
/// <returns>A point on the line defined by pt1->pt2</returns>
private static PDFPoint ComputeIntermediatePoint(PDFPoint pt1, PDFPoint pt2, double smoothFactor, bool isEndLocation)
{
    if (isEndLocation)
    {
        smoothFactor = 1 - smoothFactor;
    }

    PDFPoint intermediate = new PDFPoint();
    if (pt1.X == pt2.X)
    {
        intermediate.X = pt1.X;
        intermediate.Y = pt1.Y + (pt2.Y - pt1.Y) * smoothFactor;
    }
    else
    {
        intermediate.X = pt1.X + (pt2.X - pt1.X) * smoothFactor;
        intermediate.Y = (intermediate.X * (pt2.Y - pt1.Y) + (pt2.X * pt1.Y - pt1.X * pt2.Y)) / (pt2.X - pt1.X);
    }

    return intermediate;
}

对于这组点:

PDFPoint[] points = new PDFPoint[] {
    new PDFPoint(50, 150), new PDFPoint(100, 200), new PDFPoint(150, 50), new PDFPoint(200, 150), new PDFPoint(250, 50) };
DrawBezierConnectedLines(page, points, pen, 0, helvetica);

结果如下:

相应的PDF文件可以在这里下载: https://github.com/o2solutions/pdf4net/blob/master/GettingStarted/BezierConnectedLines/BezierConnectedLines.pdf

【讨论】:

  • 在问题中 OP 说“我首先尝试更改线帽和线连接样式,但这并没有在线连接之间产生所需的“cuvyness”。”
  • 确实,我希望它会像使用圆形连接一样简单,但使用它们仍然会导致客户的边缘过于锋利。虽然线连接看起来很圆,但它们不够弯曲。
  • @iPDFdev:非常感谢这个算法!!!!!!这个周末我会试一试,然后告诉你进展如何。
  • 已经有一段时间了,但这里有一个更新。非常感谢 iPDFdev。这个算法效果很好。对于具有非常锐角的曲线,曲线有时实际上并没有穿过所需的点。为了解决这个问题,我使用贝塞尔曲线的功能来正确预测何时会发生这种情况,并添加一个偏移量,直到曲线实际穿过该点。再次感谢!!
猜你喜欢
  • 2017-08-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-11
  • 1970-01-01
相关资源
最近更新 更多