通过PDFBox绘制曲线 [英] Drawing Curvy Lines Through PDFBox

查看:86
本文介绍了通过PDFBox绘制曲线的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

我的问题是客户不喜欢线路之间的连接有多清晰.相反,他们希望线条之间的连接以更弯曲的方式发生.附件是客户想要什么的粗略概念的图像.请注意,虽然线条看起来很弯曲,但客户特别关心线条连接起来是否弯曲,而不是像标准折线图那样尖锐.

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

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

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

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

Using PDFBox, I have created a line chart to plot some data, and it looks much like any general line chart you will see through a google search. It also looks identical to the line chart I've attached to this question. The way the line chart drawing algorithm works is that it looks at the current point, and then the next one, and a line is drawn if a valid point is found there.

My problem is that a client does not like how sharply the lines connect with each other. Instead, they want the joins between the lines to happen in more of a curved fashion. Attached is an image of a rough idea of what the client wants. Note that although the lines look very curvy, the client specifically cares about the line joins themselves being curvy, and not sharp like in a standard line chart.

So far, I have tried using Bézier curves, but I can't seem to find the right values to make it scale right for all the different magnitudes between the points. I first tried changing the line cap and line join styles, but this did not produce the desired "cuvyness" between the line joins. I have also contemplated using paths to achieve this result, but I haven't managed to figure out how to proceed.

Is there something that I'm missing that could make it easier to draw these lines? If not, can anyone help me figure out the right Bézier values/paths to achieve these curves? Thanks in advance for any suggestions/code examples.

Due to an NDA, I cannot give a code example that shows how the chart is drawn and plotted (this would give up our algorithm entirely). All that I can say is that I created an internal representation for how the data should be plotted in the chart, and this system is very roughly translated in the provided image. I can say that the function that plots the data exclusively uses the PDPageContentStream classes's lineTo and strokeTo functions, after an initial moveTo to the position of the starting point based on our internal coordinate representation.

A rough idea of the curves the client wants.

解决方案

---A quick "solution" is to use round line joins instead of miter joins (the default) --- it seems that I missed this.

The charts in your sample probably use curve interpolation and this question and answers might help you: How does polyline simplification in Adobe Illustrator work?

The code below shows how to transform a list of lines into Bezier connected lines (it's C# but it can be converted to Java with minimal changes):

/// <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;
}

For this set of points:

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);

this the result:

The corresponding PDF file can be downloaded here: https://github.com/o2solutions/pdf4net/blob/master/GettingStarted/BezierConnectedLines/BezierConnectedLines.pdf

这篇关于通过PDFBox绘制曲线的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆