本项目主要技术是jfreechart和itextpdf,纯Java生成pdf图表,我给大家分享一下完成过程中遇到的各种坑.本项目适合于需要生成pdf图表又没有html模板的需求.
我尝试过freemarker,失败在模板制作上.使用word制作模板然后转成html,再使用freemarker根据html模板生成pdf,但word只能制作带表格和文本的模板,折线图这种图表只能作为图片放在模板里,这样就不能插入数据,故而失败.哪位同学知道如何制作图表模板的请不吝赐教.
还尝试过 abel533 / ECharts,我想到每次生成图表的需求可能不一样,所以想到从echarts拿到这些图表直接用,但这个我没能生成,哪位同学想试试的可以试一下
http://hzhcontrols.com/new-566194.html
还尝试过使用js的方式,找前端朋友写一个只有表格和文本的js,测试一下可以导出成pdf,然后将echarts的图表导出为js,嵌入第一个js里,再导出就发现pdf里echarts的位置是一片空白.
给大家提供一个根据html生成pdf的短小精悍的demo:
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import org.xhtmlrenderer.pdf.ITextFontResolver;
import org.xhtmlrenderer.pdf.ITextRenderer;
import com.lowagie.text.pdf.BaseFont;
public class aaa {
/**
* @param args
*/
public static void main(String[] args) throws Exception {
// Todo Auto-generated method stub
String inputFile = "src/main/resources/pdfPage.html";
String url = new File(inputFile).toURI().toURL().toString();
String outputFile = "D://test.pdf";
System.out.println(url);
OutputStream os = new FileOutputStream(outputFile);
ITextRenderer renderer = new ITextRenderer();
renderer.setDocument(url); // 解决中文支持问题
ITextFontResolver fontResolver = renderer.getFontResolver();
fontResolver.addFont("C:/Windows/Fonts/SimsUN.TTC", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); // 解决图片的相对路径问题
renderer.getSharedContext().setBaseURL("file:/D:/z/temp/");
renderer.layout();
renderer.createPDF(os);
os.close();
}
}
最后找到了一个用无界浏览器生成的方法,相当于是在java里搭建一个浏览器,然后把页面整个画好,包括图表表格等等,然后把整个页面导出为图片,再转为pdf,整个过程很复杂,感兴趣的同学来找我要吧
正文开始~
开始之前先放一个整体效果
首先是依赖
<!--用于jfreechart生成图片 -->
<dependency>
<groupId>org.jfree</groupId>
<artifactId>jfreechart</artifactId>
<version>1.5.0</version>
</dependency>
<!-- 应该是亚洲字体支持 -->
<!-- 一定要加,否则会出现字体找不到错误 -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>
<!-- 中文字体支持 -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>font-asian</artifactId>
<version>7.1.13</version>
</dependency>
<!--用于生成pdf-->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.4</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
然后是测试类
import com.example.util.CreatePdf;
import com.example.util.Datas;
import com.example.dto.TableValue;
import com.itextpdf.text.DocumentException;
import org.apache.commons.io.FileUtils;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
@SpringBootTest
class Test1ApplicationTests {
@Test
void GeneratePdf() throws IOException, DocumentException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
TableValue tableValue = Datas.formData();
// CreateEchartsPdfUtils.createTable(outputStream,tableContentList,tableContentList2,createLinePorts(), tableContentList3,createPiePort(),tableContentList4,createLinePort(),createBarChartsPort(),createBarChartPort(),createStackedBarChartsPort());
CreatePdf.createPdf(outputStream,tableValue);
FileUtils.copyInputStreamToFile(new ByteArrayInputStream(outputStream.toByteArray()),new File("D:\\折线图/demo2.pdf"));
}
先来个简单表格试试水
1.表格
数据源
import com.example.dto.TableValue;
public class Datas {
public static TableValue formData() {
TableValue tableValue = new TableValue();
tableValue.setIndexValue1("1");
tableValue.setIndexValue2("2");
tableValue.setIndexValue3("3");
tableValue.setIndexValue4("4");
return tableValue;
}
主要框架
import com.example.dto.TableValue;
import com.example.dto.ThreeLineColor;
import com.itextpdf.text.*;
import com.itextpdf.text.pdf.PdfWriter;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class CreatePdf {
public static void createPdf(ByteArrayOutputStream outputStream,
TableValue tableValue) throws DocumentException, IOException {
// 创建一个文档(默认大小A4,边距36, 36, 36, 36)
Document document = new Document();
// 设置文档大小
document.setPageSize(PageSize.A4);
// 设置边距,单位都是像素,换算大约1厘米=28.33像素
document.setMargins(50, 50, 50, 50);
// 创建writer,通过writer将文档写入磁盘
PdfWriter writer = PdfWriter.getInstance(document, outputStream);
// 打开文档,只有打开后才能往里面加东西
document.open();
// 设置作者
document.addAuthor("作者");
// 设置创建者
document.addCreator("创建者");
//设置封面图片
Image image = Image.getInstance("src/main/resources/static/萌猫.jpg");
image.setAlignment(Image.ALIGN_LEFT);
image.scaleAbsolute(500, 300);
document.add(image);
//设置标题
document.add(CreateEchartsPdfUtils.createHead2("生成pdf图表demo", Font.NORMAL, Element.ALIGN_CENTER));
document.add(CreateEchartsPdfUtils.createHead3("01 表格", Font.BOLD, Element.ALIGN_LEFT));
//第一个表格
oneForm(document, tableValue);
document.close();
writer.close();
}
/**
* 文件传输量表格
*
* @param
* @return
*/
private static void oneForm(Document document, TableValue tableValue) throws DocumentException, IOException {
String[] oneForm = {"抖音", "微博", "虎牙", "小红书"};
CreateEchartsPdfUtils.Form(document, tableValue, 4, oneForm, ThreeLineColor.danhui);
}
}
多行表格的话传入list即可
输出pdf

需要注意的是,表格里的列数假如设置的是4列,数据源如果不足4个的话,整行就会不显示
2.日历图
日历图其实也是表格,只不过把表格的框线全部隐藏了,并添加了不同的背景颜色
/**
* 创建表格内容
*
* @param table 表格对象
* @param tableContentList 表格内容对象
*/
private static void createTableTwoCellCalendar(PdfPTable table, List<TableValue> tableContentList, BaseColor baseColor) throws DocumentException, IOException {
if (ObjectUtils.isEmpty(tableContentList)) {
return;
}
int j = 0;
for (int i = 0; i < tableContentList.size(); i++) {
TableValue TableValue = tableContentList.get(i);
String date = TableValue.getIndexValue1();
if (StringUtils.isBlank(date)) {
return;
}
String[] split = date.split("-");
//计算每月1号是星期几.以周四为例,返回四
int weeksOfDate = DateUtils.getWeeksOfDate(Integer.valueOf(split[0]), Integer.valueOf(split[1]), 1);
//返回4 前面就空4格 返回0就不空
if (j < weeksOfDate) {
table.addCell(createTableContentCalendar("", ""));
j++;
i--;
continue;
}
//获取设置表格内容的字体与内容
if (i % 2 != 0) {
table.addCell(createTableContentCalendar(split[2], TableValue.getIndexValue2(), TableValue.getIndexValue3(), baseColor));
} else {
table.addCell(createTableContentCalendar(split[2], TableValue.getIndexValue2(), TableValue.getIndexValue3()));
}
}
//一行内如果数据不足列数就不显示,为了让他显示,特地写了6个空字符串
table.addCell(createTableContentCalendar("", ""));
table.addCell(createTableContentCalendar("", ""));
table.addCell(createTableContentCalendar("", ""));
table.addCell(createTableContentCalendar("", ""));
table.addCell(createTableContentCalendar("", ""));
table.addCell(createTableContentCalendar("", ""));
}
3.折线图
/**
* 生成折线图
*
* @param datas 数据
* @param xName X轴
* @param yName Y轴
* @return 生成的折线图byte数组
*/
public static void createLinePortImage(Document document, Map<String, Map<String, BigDecimal>> datas, String xName, String yName) throws DocumentException, IOException {
try {
//种类数据集
DefaultCategoryDataset dataset = new DefaultCategoryDataset();
//遍历map
for (Map.Entry<String, Map<String, BigDecimal>> entry : datas.entrySet()) {
String key = entry.getKey();
Map<String, BigDecimal> value = entry.getValue();
for (Map.Entry<String, BigDecimal> decimalEntry : value.entrySet()) {
dataset.setValue(decimalEntry.getValue(),
key,
decimalEntry.getKey());
}
}
//创建2D折线图,折线图分水平显示和垂直显示两种
//legend:是否显示图例(对于简单的柱状图必须是false)
//tooltips:是否生成工具
//urls:是否生成URL链接
JFreeChart chart = ChartFactory.createLineChart("", xName, yName, dataset,
PlotOrientation.VERTICAL,
true,
true,
true);
//得到绘图区
setLineRender((CategoryPlot) chart.getPlot(), true, true);
ByteArrayOutputStream bas = new ByteArrayOutputStream();
ChartUtils.writeChartAsJPEG(bas, 1.0f, chart, 800, 500, null);
com.itextpdf.text.Image image = com.itextpdf.text.Image.getInstance(bas.toByteArray());
image.setAlignment(com.itextpdf.text.Image.ALIGN_CENTER);
image.scaleAbsolute(500, 300);
document.add(image);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 设置折线图样式
*
* @param plot 折线图对象
* @param isShowDataLabels 是否显示数据标题
* @param isShapesVisible 是否显示数据点
*/
public static void setLineRender(CategoryPlot plot, boolean isShowDataLabels, boolean isShapesVisible) {
plot.setNoDataMessage(NO_DATA_MSG);
plot.setInsets(new RectangleInsets(10, 10, 0, 10), false);
LineAndShapeRenderer renderer = (LineAndShapeRenderer) plot.getRenderer();
renderer.setDefaultStroke(new BasicStroke(1.5F));
if (isShowDataLabels) {
renderer.setDefaultItemLabelsVisible(true);
renderer.setDefaultItemLabelGenerator(new StandardCategoryItemLabelGenerator(StandardCategoryItemLabelGenerator.DEFAULT_LABEL_FORMAT_STRING,
NumberFormat.getInstance()));
renderer.setDefaultPositiveItemLabelPosition(new ItemLabelPosition(ItemLabelAnchor.OUTSIDE1, TextAnchor.BOTTOM_CENTER));
}
// 数据点绘制形状
//显示数据点
renderer.setDefaultShapesVisible(isShapesVisible);
// renderer.setSeriesShapesVisible(0,isShapesVisible);
//开启填充色
renderer.setUseFillPaint(isShapesVisible);
//数据点填充的颜色
renderer.setSeriesFillPaint(0, Color.white);
//开启外廓线(填充色开启后有效)
renderer.setDrawOutlines(isShapesVisible);
//使用外廓线颜色
renderer.setUseOutlinePaint(isShapesVisible);
//设置外框线颜色
// renderer.setSeriesOutlinePaint(0,Color.yellow);
//设置折线的颜色
renderer.setSeriesPaint(0, ThreeLineColor.sGreen);
renderer.setSeriesPaint(1, ThreeLineColor.zhaxianlan);
renderer.setSeriesPaint(2, ThreeLineColor.zhexianlan);
//设置外廓线大小
renderer.setSeriesOutlineStroke(0, new BasicStroke(2.0F));
//设置线条粗细(setDefaultStroke和这个方法冲突)
renderer.setSeriesStroke(0, new BasicStroke(1.0F));
//圆形数据点
// renderer.setSeriesShape(0, new Ellipse2D.Double(-2.0D,-2.0D,5.0D,5.0D) );
plot.setRenderer(renderer);
setXAixs(plot, CategoryLabelPositions.STANDARD);
setYAixs(plot);
}
x轴和y轴的设置
/**
* 设置类别图表(CategoryPlot) X坐标轴线条颜色和样式
*
* @param plot 类别图表对象 categoryLabelPositions x轴文字倾斜度
*/
public static void setXAixs(CategoryPlot plot, CategoryLabelPositions categoryLabelPositions) {
Color lineColor = new Color(51, 51, 51);
//STANDARD:x轴的字体倾斜度
CategoryAxis axis = plot.getDomainAxis();
axis.setCategoryLabelPositions(categoryLabelPositions);
// X坐标轴颜色
axis.setAxisLinePaint(lineColor);
// X坐标轴标记|竖线颜色
axis.setTickMarkPaint(lineColor);
axis.setLabelFont(FONT);
// font = Font.createFont(Font.TRUETYPE_FONT,new FileInputStream("/usr/share/fonts/cjkuni-uming/uming.ttc"));
axis.setTickLabelFont(FONT);
}
/**
* 设置类别图表(CategoryPlot) Y坐标轴线条颜色和样式 同时防止数据无法显示
*
* @param plot 类别图表对象
*/
public static void setYAixs(CategoryPlot plot) {
Color axisColor = new Color(51, 51, 51);
Color tickColor = new Color(219, 218, 218);
ValueAxis axis = plot.getRangeAxis();
// Y坐标轴颜色
axis.setAxisLinePaint(axisColor);
// Y坐标轴标记|竖线颜色
axis.setTickMarkPaint(tickColor);
// false隐藏Y刻度
axis.setAxisLineVisible(true);
axis.setTickMarksVisible(true);
// Y轴网格线条
plot.setRangeGridlinePaint(new Color(229, 229, 229));
plot.setRangeGridlineStroke(new BasicStroke(1));
// 设置顶部Y坐标轴间距,防止数据无法显示
axis.setUpperMargin(0.1);
// 设置底部Y坐标轴间距
axis.setLowerMargin(0.1);
axis.setLabelFont(FONT);
axis.setTickLabelFont(FONT);
//设置y最小取值范围.如果数据全是0,那么0这条折线将显示在y轴中间,y轴0刻度以下将用负数表示,
// 但是是显示的是 0E0,-1E-9,-2E-9,-3E-9,-4E-9,这样用科学计数法表示的数,加上这个可
// 以让y轴在数据为0的时候折线在0的水平位置,接近于x轴.但是y轴刻度仍任是科学计数法.不介意的话可以用
//还可以设置最大值,这样的话y轴刻度就不会自动生成了,就是0-1000
/* axis.setLowerBound(0);
axis.setUpperBound(1000);*/
}
效果是这样子

大家有没有发现,x轴的字怎么模糊不清呢?是不是字体太小了呢?

放大也是这样,这事因为x轴的内容是 2023-01-01这样的日期,太长了.
将 setLineRender方法末尾的setXAixs(plot, CategoryLabelPositions.STANDARD)修改为CategoryLabelPositions.UP_45,将字体倾斜45度,效果如下

折线和数据点的颜色,数据点的形状等等都是可以自由调节的.
4.饼状图
/**
* 生成饼图
*
* @param datas 数据
* @return 返回生成的byte数组
*/
public static void createPiePortImage(Document document, Map<String, BigDecimal> datas) throws DocumentException, IOException {
try {
// 如果不使用Font,中文将显示不出来
DefaultPieDataset pds = new DefaultPieDataset();
//遍历map
for (Map.Entry<String, BigDecimal> entry : datas.entrySet()) {
pds.setValue(entry.getKey(),
entry.getValue());
}
/**
* 生成一个饼图的图表
* 分别是:显示图表的标题、需要提供对应图表的DateSet对象、是否显示图例、是否生成贴士以及是否生成URL链接
*/
JFreeChart chart = ChartFactory.createPieChart("", pds, true, true, true);
setPieRender(chart.getPlot());
ByteArrayOutputStream bas = new ByteArrayOutputStream();
ChartUtils.writeChartAsJPEG(bas, 1.0f, chart, 800, 500, null);
com.itextpdf.text.Image image = com.itextpdf.text.Image.getInstance(bas.toByteArray());
image.setAlignment(com.itextpdf.text.Image.ALIGN_CENTER);
image.scaleAbsolute(500, 300);
document.add(image);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 设置饼状图渲染
*
* @param plot 饼图对象
*/
public static void setPieRender(Plot plot) {
plot.setNoDataMessage(NO_DATA_MSG);
plot.setInsets(new RectangleInsets(10, 10, 5, 10));
PiePlot piePlot = (PiePlot) plot;
piePlot.setInsets(new RectangleInsets(0, 0, 0, 0));
// 圆形
piePlot.setCircular(true);
piePlot.setLabelGap(0.01);
piePlot.setInteriorGap(0.05D);
// 图例形状
piePlot.setLegendItemShape(new Rectangle(10, 10));
piePlot.setIgnoreNullValues(true);
// 去掉背景色
piePlot.setLabelBackgroundPaint(null);
// 去掉阴影
piePlot.setLabelShadowPaint(null);
// 去掉边框
piePlot.setLabelOutlinePaint(null);
piePlot.setShadowPaint(null);
// 0:category 1:value:2 :percentage
// 显示标签数据 {0} 代表 饼状图分类的名字,{1} 显示具体数值,{2} 显示百分比
//{0}:{1} 英雄联盟:20 {0}:{2} 英雄联盟 20%
piePlot.setLabelGenerator(new StandardPieSectionLabelGenerator("{0}:{1}"));
}
饼状图挺简单的,需要注意的就是值的显示 {0}:{1}显示的就是

{0}:{2}显示的就是百分比
5.柱状图
/**
* 生成柱状图
*
* @param datas 数据
* @return
*/
public static void createBarChartImage(Document document, Map<String, Map<String, BigDecimal>> datas) throws DocumentException, IOException {
try {
//种类数据集
DefaultCategoryDataset dataset = new DefaultCategoryDataset();
//遍历map
for (Map.Entry<String, Map<String, BigDecimal>> entry : datas.entrySet()) {
String key = entry.getKey();
Map<String, BigDecimal> value = entry.getValue();
for (Map.Entry<String, BigDecimal> decimalEntry : value.entrySet()) {
dataset.setValue(decimalEntry.getValue(),
key,
decimalEntry.getKey());
}
}
//legend:是否显示图例(对于简单的柱状图必须是false)
//tooltips:是否生成工具
//urls:是否生成URL链接
JFreeChart chart = ChartFactory.createBarChart("", "", "", dataset,
PlotOrientation.VERTICAL,
true,
true,
false);
//得到绘图区
setBarChartRender((CategoryPlot) chart.getPlot(), true, true);
ByteArrayOutputStream bas = new ByteArrayOutputStream();
ChartUtils.writeChartAsJPEG(bas, 1.0f, chart, 800, 500, null);
com.itextpdf.text.Image image = com.itextpdf.text.Image.getInstance(bas.toByteArray());
image.setAlignment(com.itextpdf.text.Image.ALIGN_CENTER);
image.scaleAbsolute(500, 300);
document.add(image);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 设置柱状图样式
*
* @param plot 折线图对象
* @param isShowDataLabels 是否显示数据标题
* @param isShapesVisible 是否显示数据点
*/
public static void setBarChartRender(CategoryPlot plot, boolean isShowDataLabels, boolean isShapesVisible) {
plot.setNoDataMessage(NO_DATA_MSG);
plot.setInsets(new RectangleInsets(10, 10, 0, 10), false);
BarRenderer renderer = (BarRenderer) plot.getRenderer();
renderer.setDefaultStroke(new BasicStroke(0F));
if (isShowDataLabels) {
renderer.setDefaultItemLabelsVisible(true);
renderer.setDefaultItemLabelGenerator(new StandardCategoryItemLabelGenerator(StandardCategoryItemLabelGenerator.DEFAULT_LABEL_FORMAT_STRING,
NumberFormat.getInstance()));
renderer.setDefaultPositiveItemLabelPosition(new ItemLabelPosition(ItemLabelAnchor.OUTSIDE12, TextAnchor.BASELINE_CENTER));
}
renderer.setDefaultSeriesVisible(true);
//设置柱子的宽度
renderer.setMaximumBarWidth(0.05);
//设置柱子的高度
// renderer.setMinimumBarLength(0.2);
//设置柱子边框线可见
// renderer.setDrawBarOutline(isShapesVisible);
//设置柱子边框线的颜色
// renderer.setSeriesOutlinePaint(0, Color.green);
//设置每个地区所包含的平行柱的距离
// renderer.setItemMargin(0.5);
//显示每个柱的数值,并修改该数值的字体属性
renderer.setIncludeBaseInRange(true);
//柱子的透明度
plot.setForegroundAlpha(1.0f);
//柱子的文字偏离值
// renderer.setItemLabelAnchorOffset(10D);
//设置柱子的颜色
renderer.setSeriesPaint(0, Color.yellow);
//设置外廓线大小
/* renderer.setSeriesOutlineStroke(0,new BasicStroke(2.0F));
//设置线条粗细
renderer.setSeriesStroke(0,new BasicStroke(2.0F));*/
setXAixs(plot, CategoryLabelPositions.STANDARD);
setYAixs(plot);
}
柱子的颜色,透明度,都是可以设置的,字体也可以设置为柱体内,柱体上,柱体旁

6.堆叠柱状图
JFreeChart chart = ChartFactory.createStackedBarChart("", "", "", dataset,
PlotOrientation.VERTICAL,
true,
true,
false);
//得到绘图区
只需将ChartFactory的createBarChart改为createStackedBarChart就可以
7.柱状图和折线图的组合图
第一种是y轴分离,相当于两个图合在一起,共享x轴

/**
* 生成柱状图混合折线图
* 柱状图和折线图共享x轴,y轴是分隔开的,相当于是柱状图上面有个折线图.
* @param datas 数据
* @return
*/
public static void createBarChartAndLineImage(Document document, Map<String, Map<String, BigDecimal>> datas) throws DocumentException, IOException {
try {
//种类数据集
DefaultCategoryDataset dataset = new DefaultCategoryDataset();
//遍历map
for (Map.Entry<String, Map<String, BigDecimal>> entry : datas.entrySet()) {
String key = entry.getKey();
Map<String, BigDecimal> value = entry.getValue();
for (Map.Entry<String, BigDecimal> decimalEntry : value.entrySet()) {
dataset.setValue(decimalEntry.getValue(),
key,
decimalEntry.getKey());
}
}
//创建数据源一的纵坐标对象。
NumberAxis rangeAxis1 = new NumberAxis("Value");
LineAndShapeRenderer renderer1 = new LineAndShapeRenderer();
//我们在这里将横坐标设置为NULL,表示不使用自己的横坐标对象,只使用自己的纵坐标和图表渲染对象(折线图)
CategoryPlot subplot1 = new CategoryPlot(dataset, null, rangeAxis1,
renderer1);
//纵坐标对象。
NumberAxis rangeAxis2 = new NumberAxis("Value");
//柱状图的图表渲染对象。
BarRenderer renderer2 = new BarRenderer();
//同样的不是用横坐标对象。
CategoryPlot subplot2 = new CategoryPlot(dataset, null, rangeAxis2, renderer2);
CategoryAxis domainAxis = new CategoryAxis("Category");
//先合并横坐标。
CombinedDomainCategoryPlot plot = new CombinedDomainCategoryPlot(
domainAxis);
//添加纵坐标。
plot.add(subplot1, 2);
//添加纵坐标。
plot.add(subplot2, 1);
JFreeChart result = new JFreeChart(plot);
ByteArrayOutputStream bas = new ByteArrayOutputStream();
ChartUtils.writeChartAsJPEG(bas, 1.0f, result, 800, 500, null);
// ChartUtils.writeChartAsJPEG(bas, 1.0f, line, 800, 500, null);
com.itextpdf.text.Image image = com.itextpdf.text.Image.getInstance(bas.toByteArray());
image.setAlignment(com.itextpdf.text.Image.ALIGN_CENTER);
image.scaleAbsolute(500, 300);
document.add(image);
} catch (Exception e) {
e.printStackTrace();
}
}
第二种是双y轴,折线图和柱状图有交互的组合图

/**
* 生成柱状图混合折线图
*双y轴,柱状图和折线图在一张图里
* @param datas 数据
* @return
*/
public static void createChart(Document document, Map<String, Map<String, BigDecimal>> datas) throws DocumentException, IOException {
try {
//种类数据集
DefaultCategoryDataset dataset = new DefaultCategoryDataset();
//遍历map
for (Map.Entry<String, Map<String, BigDecimal>> entry : datas.entrySet()) {
String key = entry.getKey();
Map<String, BigDecimal> value = entry.getValue();
for (Map.Entry<String, BigDecimal> decimalEntry : value.entrySet()) {
dataset.setValue(decimalEntry.getValue(),
key,
decimalEntry.getKey());
}
}
// 初始化一个基础渲染规则为3D模式的柱状统计图效果的Chart图表。
JFreeChart chart = ChartFactory.createBarChart("DoubleBarChart", "Category", "Value", dataset, PlotOrientation.VERTICAL, true, true, false);
// 获取绘图区对象
CategoryPlot plot = (CategoryPlot) chart.getPlot();
// 设置轴1--数据匹配
NumberAxis axis0 = new NumberAxis("第一条轴线");
plot.setRangeAxis(0, axis0);
plot.setDataset(0, dataset);
plot.mapDatasetToRangeAxis(0, 0);
// 重新生成一个图表渲染的对象(折线图渲染对象)。
LineAndShapeRenderer lineandshaperenderer = new LineAndShapeRenderer();
// 显示折点数据。
lineandshaperenderer.setDefaultItemLabelsVisible(true);
lineandshaperenderer.setDefaultItemLabelGenerator(new StandardCategoryItemLabelGenerator());
// 设置拐点是否可见/是否显示拐点
lineandshaperenderer.setDefaultShapesVisible(true);
// 设置线条是否被显示填充颜色
lineandshaperenderer.setUseFillPaint(true);
// 设置第一条折线的拐点颜色
lineandshaperenderer.setSeriesFillPaint(0, Color.BLUE);
// 设置第二条折线的拐点颜色
lineandshaperenderer.setSeriesFillPaint(1, Color.RED);
//设置折线颜色(第一条折线数据线)
lineandshaperenderer.setSeriesPaint(0, new Color(91, 155, 213));
//设置折线颜色(第二条折折线据线)
lineandshaperenderer.setSeriesPaint(1, Color.RED);
// 设置第一条折线的广度(粗细度)
lineandshaperenderer.setSeriesStroke(0, new BasicStroke(1.8F));
// 设置第二条折线的广度(粗细度)
lineandshaperenderer.setSeriesStroke(1, new BasicStroke(1.8F));
//设置拐点数值颜色,默认黑色
lineandshaperenderer.setDefaultItemLabelsVisible(true); // 默认就是true,这里可以不用刻意声明。
lineandshaperenderer.setDefaultItemLabelPaint(Color.BLUE);
// 解决最高柱体或折点提示内容被遮盖的问题。
lineandshaperenderer.setDefaultPositiveItemLabelPosition(new ItemLabelPosition(ItemLabelAnchor.OUTSIDE12, TextAnchor.BASELINE_CENTER));
lineandshaperenderer.setItemLabelAnchorOffset(2); // 设置柱形图上的文字偏离值
// 重构第二个数据对象的渲染方式,由现在默认的Bar(柱状统计图)重构为刚刚初始化的Line(折线统计图)的渲染模式
plot.setRenderer(1, lineandshaperenderer);
// 设置轴2--数据匹配
NumberAxis axis1 = new NumberAxis("第二条轴线");
plot.setRangeAxis(1, axis1);
plot.setDataset(1, dataset);
plot.mapDatasetToRangeAxis(1, 1);
plot.setDatasetRenderingOrder(DatasetRenderingOrder.FORWARD);
/** ---------------------- 中文乱码问题处理 Start ------------------------------- */
CategoryAxis domainAxis = plot.getDomainAxis(); //水平底部列表
domainAxis.setLabelFont(new Font("黑体", Font.BOLD, 14)); //水平底部标题
domainAxis.setTickLabelFont(new Font("宋体", Font.BOLD, 12)); //垂直标题
ValueAxis rangeAxis = plot.getRangeAxis();//获取柱状
rangeAxis.setLabelFont(new Font("黑体", Font.BOLD, 15));
chart.getLegend().setItemFont(new Font("黑体", Font.BOLD, 15));
chart.getTitle().setFont(new Font("宋体", Font.BOLD, 20));//设置标题字体
/** ---------------------- 中文乱码问题处理 End ------------------------------- */
rangeAxis.setAutoRange(true);
// 设置图表控件的背景颜色。
chart.setBackgroundPaint(Color.WHITE);
ByteArrayOutputStream bas = new ByteArrayOutputStream();
ChartUtils.writeChartAsJPEG(bas, 1.0f, chart, 800, 500, null);
// ChartUtils.writeChartAsJPEG(bas, 1.0f, line, 800, 500, null);
com.itextpdf.text.Image image = com.itextpdf.text.Image.getInstance(bas.toByteArray());
image.setAlignment(com.itextpdf.text.Image.ALIGN_CENTER);
image.scaleAbsolute(500, 300);
document.add(image);
} catch (Exception e) {
e.printStackTrace();
}
}
有这种的




tips1
每一个图表,我都是先写标题,再画图表
document.add(CreateEchartsPdfUtils.createHead3("01 表格", Font.BOLD, Element.ALIGN_LEFT));
//第一个表格
oneForm(document, tableValue);
//日历图
document.add(CreateEchartsPdfUtils.createHead3("02 日历图", Font.BOLD, Element.ALIGN_LEFT));
CreateEchartsPdfUtils.calendarTwoForm(document, calendar);
//折线图
document.add(CreateEchartsPdfUtils.createHead3("03 折线图", Font.BOLD, Element.ALIGN_LEFT));
JFreeChartUtils.createLinePortImage(document, linePorts, "日期", "数量");
//饼状图
document.add(CreateEchartsPdfUtils.createHead3("04 饼状图", Font.BOLD, Element.ALIGN_LEFT));
JFreeChartUtils.createPiePortImage(document, piePort);
//柱状图
document.add(CreateEchartsPdfUtils.createHead3("05 柱状图", Font.BOLD, Element.ALIGN_LEFT));
JFreeChartUtils.createBarChartImage(document, barChartPort);
//堆叠柱状图
document.add(CreateEchartsPdfUtils.createHead3("06 堆叠柱状图", Font.BOLD, Element.ALIGN_LEFT));
JFreeChartUtils.createStackedBarChart(document, stackedBarChartsPort);
//柱状图和折线图组合图1
document.add(CreateEchartsPdfUtils.createHead3("07 柱状图和折线图组合图1", Font.BOLD, Element.ALIGN_LEFT));
JFreeChartUtils.createBarChartAndLineImage(document, linePorts);
//柱状图和折线图组合图2 双y轴
document.add(CreateEchartsPdfUtils.createHead3("08 柱状图和折线图组合图2", Font.BOLD, Element.ALIGN_LEFT));
JFreeChartUtils.createChart(document, linePorts);
但是实际结果可能是这样的

这可能是jfreechart为了节省空间自动做的排版,遇到这样的情况大家可以先开发,开发完成后统一处理,可以在柱状图代码后加上document.newPage();

tips2
上linux测试环境进行测试后,遇到一个关于字体的问题
private static Font createFont(int fontSize, int fontMode, BaseColor fontColor) throws DocumentException, IOException {
BaseFont bfChinese = BaseFont.createFont("STSongStd-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
return new Font(bfChinese, fontSize, fontMode, fontColor);
}
这是创建标题时createHead3 所用的,Font是itext下的包,测试环境可以正常创建标题,正常显示
而折线图的x轴所用的字体是awt的字体
private static final Font FONT = new Font("STSongStd-Light", Font.BOLD, 12);
awt的字体在测试环境找不到,所以自动找了个默认字体,贼丑,而且不清晰.
和itext的font相比,awt的font没有编码方式
只找到了itext的字体转awt字体的方法,没有找到awt转itext的方法
试过直接写字体文件名,写字体绝对路径,将字体转成流,都不行.最后将时间由2023-01-02格式改为日期 02,CategoryLabelPositions.UP_45修改为STANDARD,勉强再用.如果有懂的大佬指导下我应该怎么解决,如果你也遇到这个问题不知道怎么解决的话可以用我这个方法
tips3
大家生成pdf肯定都是调用好几个接口,等候接口返回数据再调下一个接口,整体响应时间太长,可以用CompletableFuture进行异步调用
原来取数据:
@Test
void GeneratePdf() throws IOException, DocumentException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
TableValue tableValue = Datas.formData();
List<TableValue> calendar = Datas.calendar();
Map<String, Map<String, BigDecimal>> linePorts = Datas.createLinePorts();
Map<String, BigDecimal> piePort = Datas.createPiePort();
Map<String, Map<String, BigDecimal>> barChartPort = Datas.createBarChartPort();
Map<String, Map<String, BigDecimal>> stackedBarChartsPort = Datas.createStackedBarChartsPort();
// CreateEchartsPdfUtils.createTable(outputStream,tableContentList,tableContentList2,createLinePorts(), tableContentList3,createPiePort(),tableContentList4,createLinePort(),createBarChartsPort(),createBarChartPort(),createStackedBarChartsPort());
CreatePdf.createPdf(outputStream,tableValue,calendar,linePorts,piePort,barChartPort,stackedBarChartsPort);
FileUtils.copyInputStreamToFile(new ByteArrayInputStream(outputStream.toByteArray()),new File("D:\\折线图/demo2.pdf"));
}
使用CompletableFuture以后
@Test
void GeneratePdf() throws IOException, DocumentException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
// TableValue tableValue = Datas.formData();
CompletableFuture<TableValue> tableValue = CompletableFuture.supplyAsync(() -> Datas.formData());
// List<TableValue> calendar = Datas.calendar();
CompletableFuture<List<TableValue>> calendar = CompletableFuture.supplyAsync(() -> Datas.calendar());
// Map<String, Map<String, BigDecimal>> linePorts = Datas.createLinePorts();
CompletableFuture<Map<String, Map<String, BigDecimal>>> linePorts = CompletableFuture.supplyAsync(() -> Datas.createLinePorts());
// Map<String, BigDecimal> piePort = Datas.createPiePort();
CompletableFuture<Map<String, BigDecimal>> piePort = CompletableFuture.supplyAsync(() -> Datas.createPiePort());
// Map<String, Map<String, BigDecimal>> barChartPort = Datas.createBarChartPort();
CompletableFuture<Map<String, Map<String, BigDecimal>>> barChartPort = CompletableFuture.supplyAsync(() -> Datas.createBarChartPort());
// Map<String, Map<String, BigDecimal>> stackedBarChartsPort = Datas.createStackedBarChartsPort();
CompletableFuture<Map<String, Map<String, BigDecimal>>> stackedBarChartsPort = CompletableFuture.supplyAsync(() -> Datas.createStackedBarChartsPort());
CompletableFuture.allOf(tableValue,calendar,linePorts,piePort,barChartPort,stackedBarChartsPort);
// CreateEchartsPdfUtils.createTable(outputStream,tableContentList,tableContentList2,createLinePorts(), tableContentList3,createPiePort(),tableContentList4,createLinePort(),createBarChartsPort(),createBarChartPort(),createStackedBarChartsPort());
CreatePdf.createPdf(outputStream,tableValue.join(),calendar.join(),linePorts.join(),piePort.join(),barChartPort.join(),stackedBarChartsPort.join());
FileUtils.copyInputStreamToFile(new ByteArrayInputStream(outputStream.toByteArray()),new File("D:\\折线图/demo2.pdf"));
}
tips4
以上都是直接将文件在本地生成,实际使用中需要前端发送请求,我们将文件流给前端返回,该怎么做呢
public void generateEDBPdf(HttpServletResponse response) throws IOException {
String name = "要定义的文件名称";
response.setHeader("Content-Disposition", "attachment;filename=" + name + ".pdf");
response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
response.setContentType("application/pdf;charset=UTF-8");
ServletOutputStream output = response.getOutputStream();
BufferedOutputStream outputStream = new BufferedOutputStream(output);
// CreatePdf.createPdf(outputStream,tableValue.join(),calendar.join(),linePorts.join(),piePort.join(),barChartPort.join(),stackedBarChartsPort.join());
}
将CreatePdf.createPdf这个方法里的outputStream改为这个controller对应的Stream就好了
项目git地址 :https://gitee.com/mhjo/minghe/tree/jfree/
大家想推代码请新建分支~