You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用iText PDF在PDF中添加多系列样条曲线图表?

使用iText绘制多系列样条曲线图表的思路与实现

核心思路

iText没有内置图表组件,需手动计算坐标并借助PdfCanvas的路径API实现样条曲线,核心步骤如下:

  • 在PDF页面划定图表的矩形画布区域
  • 将多系列数据值映射为画布内的坐标(注意PDF坐标系以左下角为原点,需做Y轴方向的转换)
  • 绘制坐标轴、刻度与标签
  • 对每个数据系列,使用curveTo()方法绘制样条曲线,同时添加图例区分不同系列

示例代码

import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.properties.TextAlignment;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class MultiSeriesSplineChart {
    // 图表区域参数
    private static final float CHART_X = 50;
    private static final float CHART_Y = 50;
    private static final float CHART_WIDTH = 500;
    private static final float CHART_HEIGHT = 300;
    // 坐标轴边距
    private static final float AXIS_MARGIN = 30;

    public static void main(String[] args) throws IOException {
        PdfWriter writer = new PdfWriter(new File("multi_spline_chart.pdf"));
        PdfDocument pdfDoc = new PdfDocument(writer);
        Document doc = new Document(pdfDoc);

        // 添加标题
        doc.add(new Paragraph("多系列样条曲线图表").setTextAlignment(TextAlignment.CENTER).setFontSize(16));

        PdfCanvas canvas = new PdfCanvas(pdfDoc.addNewPage());
        // 绘制图表边框
        canvas.rectangle(CHART_X, CHART_Y, CHART_WIDTH, CHART_HEIGHT);
        canvas.stroke();

        // 模拟多系列数据:每个系列为y值列表,x值按索引均匀分布
        List<List<Float>> seriesData = new ArrayList<>();
        seriesData.add(List.of(10f, 25f, 15f, 35f, 20f)); // 系列1
        seriesData.add(List.of(5f, 18f, 30f, 22f, 28f));  // 系列2
        List<String> seriesLabels = List.of("系列A", "系列B");
        List<int[]> seriesColors = List.of(new int[]{0, 0, 255}, new int[]{255, 0, 0}); // 蓝、红

        // 计算数据范围,预留余量
        float minY = Float.MAX_VALUE;
        float maxY = Float.MIN_VALUE;
        for (List<Float> series : seriesData) {
            for (float y : series) {
                minY = Math.min(minY, y);
                maxY = Math.max(maxY, y);
            }
        }
        minY -= 5;
        maxY += 5;
        int dataCount = seriesData.get(0).size();

        // 绘制坐标轴
        drawAxes(canvas);

        // 绘制每个系列的样条曲线与数据点
        for (int seriesIdx = 0; seriesIdx < seriesData.size(); seriesIdx++) {
            List<Float> data = seriesData.get(seriesIdx);
            int[] color = seriesColors.get(seriesIdx);
            canvas.setStrokeColor(color[0], color[1], color[2]);
            canvas.setLineWidth(2);

            // 计算数据点坐标
            List<float[]> points = new ArrayList<>();
            for (int i = 0; i < dataCount; i++) {
                float x = CHART_X + AXIS_MARGIN + (CHART_WIDTH - 2 * AXIS_MARGIN) * i / (dataCount - 1);
                float y = CHART_Y + AXIS_MARGIN + (CHART_HEIGHT - 2 * AXIS_MARGIN) * (data.get(i) - minY) / (maxY - minY);
                points.add(new float[]{x, y});
            }

            // 绘制样条曲线
            canvas.moveTo(points.get(0)[0], points.get(0)[1]);
            for (int i = 1; i < points.size(); i++) {
                float[] prev = points.get(i-1);
                float[] curr = points.get(i);
                // 取相邻点中点作为控制点,可按需调整
                float controlX = (prev[0] + curr[0]) / 2;
                canvas.curveTo(controlX, prev[1], controlX, curr[1], curr[0], curr[1]);
            }
            canvas.stroke();

            // 绘制数据点标记
            canvas.setFillColor(color[0], color[1], color[2]);
            for (float[] point : points) {
                canvas.circle(point[0], point[1], 3);
                canvas.fill();
            }
        }

        // 绘制图例
        drawLegend(canvas, seriesLabels, seriesColors);

        doc.close();
    }

    private static void drawAxes(PdfCanvas canvas) {
        // 绘制Y轴
        canvas.moveTo(CHART_X + AXIS_MARGIN, CHART_Y + AXIS_MARGIN);
        canvas.lineTo(CHART_X + AXIS_MARGIN, CHART_Y + CHART_HEIGHT - AXIS_MARGIN);
        canvas.stroke();
        // 绘制X轴
        canvas.moveTo(CHART_X + AXIS_MARGIN, CHART_Y + AXIS_MARGIN);
        canvas.lineTo(CHART_X + CHART_WIDTH - AXIS_MARGIN, CHART_Y + AXIS_MARGIN);
        canvas.stroke();

        // 添加Y轴刻度(简化示例)
        canvas.beginText();
        canvas.setFontSize(10);
        canvas.moveText(CHART_X + AXIS_MARGIN - 20, CHART_Y + AXIS_MARGIN + 5);
        canvas.showText("0");
        canvas.moveText(0, CHART_HEIGHT - 2 * AXIS_MARGIN);
        canvas.showText("40");
        canvas.endText();

        // 添加X轴刻度
        int dataCount = 5;
        for (int i = 0; i < dataCount; i++) {
            float x = CHART_X + AXIS_MARGIN + (CHART_WIDTH - 2 * AXIS_MARGIN) * i / (dataCount - 1);
            canvas.moveTo(x, CHART_Y + AXIS_MARGIN);
            canvas.lineTo(x, CHART_Y + AXIS_MARGIN - 5);
            canvas.stroke();
            canvas.beginText();
            canvas.moveText(x - 10, CHART_Y + AXIS_MARGIN - 15);
            canvas.showText("点" + (i+1));
            canvas.endText();
        }
    }

    private static void drawLegend(PdfCanvas canvas, List<String> labels, List<int[]> colors) {
        float legendX = CHART_X + CHART_WIDTH - 150;
        float legendY = CHART_Y + CHART_HEIGHT - AXIS_MARGIN - 20;

        for (int i = 0; i < labels.size(); i++) {
            // 绘制颜色块
            canvas.setFillColor(colors.get(i)[0], colors.get(i)[1], colors.get(i)[2]);
            canvas.rectangle(legendX, legendY - 15, 10, 10);
            canvas.fill();
            // 绘制标签
            canvas.beginText();
            canvas.setFontSize(10);
            canvas.moveText(legendX + 15, legendY - 5);
            canvas.showText(labels.get(i));
            canvas.endText();
            legendY -= 25;
        }
    }
}

关键细节说明

  • 坐标转换:PDF坐标系原点在页面左下角,需将数据值映射为Y坐标时,确保数值越大对应画布上的Y坐标越高。
  • 样条控制点:示例采用相邻点中点作为控制点,可根据需求调整计算逻辑,优化曲线平滑度或形状。
  • 数据适配:若各数据系列长度不同,需统一X轴刻度范围,保证所有系列能正确对齐绘制。
  • 样式定制:通过setStrokeColor()、setLineWidth()、setFontSize()等方法,可调整线条颜色、粗细、标签字体等样式。

内容的提问来源于stack exchange,提问作者Sanghamitra Lahiri

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 22:40:42