如何使用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
相关产品推荐
相关产品推荐

