700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > Java通过POI或Freemarker生成word文档 使用Jfreechart创建统计图表

Java通过POI或Freemarker生成word文档 使用Jfreechart创建统计图表

时间:2024-07-26 10:28:36

相关推荐

Java通过POI或Freemarker生成word文档 使用Jfreechart创建统计图表

最近做了一个使用Java生成统计分析报告word文档的功能,有提前制作好的word文档,其中共包含了普通文本变量,普通表格,动态表格、统计图表(柱状图、饼状图、折线图等),在此记录下POI和freemarker两种方法。

POI:

POI是使用.docx文件作为模板的,直接将word文档中需要替换的数据改为${变量名},然后通过加载模板并解析为document对象来处理,也可以直接创建一个空的dcoument对象,document通常通过段落(XWPFParagraph)、表格(XWPFTable)、和图片(XWPFPictureData)来解析整个文档。(注意:这里的段落是当格式发生变化时就是一个新的段落,而不是文档中文本的段落)

一个word文档包含多个段落,一个段落包含多个Runs,一个Runs包含多个Run,Run是文档的最小单位

获取所有段落:List<XWPFParagraph> paragraphs = document.getParagraphs();

获取一个段落中的所有Runs:List<XWPFRun> runs = paragraph.get(0).getRuns();

获取一个Runs中的一个Run:XWPFRun run = runs.get(index);

一个word文档可包含多个表格,每个表格可以包含多行,每行可以包含多列(格)。注意:每一个单元格又相当于一个完整的文档,包含了XWPFParagraph、XWPFRun

获取所有表格:List<XWPFTable> xwpfTables = doc.getTables();

获取一个表格中的所有行:List<XWPFTableRow> xwpfTableRows = xwpfTable.getRows();

获取一行中的所有列:List<XWPFTableCell> xwpfTableCells = xwpfTableRow.getTableCells();

获取一格里的内容:List<XWPFParagraph> paragraphs = xwpfTableCell.getParagraphs();

图片是直接获取到一个图片列表xwpfDocument.getAllPictures();

首先解释下XWPFParagraph,普通文本中一个段落就是单行形式,而在表格中一个单元格里也可能会有多个段落XWPFParagraph,多个XWPFRun,所以替换变量值的时候要注意,有时候会出现替换之后,原来的内容还有一部分没替换完。这个多试几次也就会发现了。其次就是POI是要解析整个word文档的,然后将之替换为实际值,因此会有大量编码。但是这样也会对文档内容、格式调整有较好的支持。

freemarker

freemarker是以.ftl文件为模板,是先将.docx进行变量替换${变量名},然后将.docx文件另存为xml文件,再改为.xml后缀改为.ftl,就得到了.ftl的模板文件,然后使用FreeMarker引擎将数据填充到模板,来动态渲染生成输出文本。(方法大家自行搜索下,很多的案例)

制作模板过程:

将 word 中需要填充的数据用占位符${变量名}替换。

将该 word 另存为 .xml 的格式,并检查看格式是否有误(重点:打开xml文件,查看占位符${变量名}是否完整,如果被分割开了,就复制完整,然后删掉多余的标签)。

将后缀.xml改成.ftl后,模板就制作完成了。

其中对于表格数据需要单独做处理,可以参考教程/weixin_45103378/article/details/118395284。

POI实现方式:主要就是要解析出所有的变量,然后进行替换,以下为部分代码片段

public class POIWordUtil {/*** 替换段落文本* @param document docx解析对象* @param textMap 需要替换的信息集合*/public static void changeText(XWPFDocument document, Map<String, Object> textMap, Map<String, Object> chartMap) throws IOException {//获取段落集合List<XWPFParagraph> paragraphs = document.getParagraphs();for (XWPFParagraph paragraph : paragraphs) {//判断此段落时候需要进行替换String text = paragraph.getText();if (checkText(text)) {List<XWPFRun> runs = paragraph.getRuns();for (XWPFRun run : runs) {//判断是否包含$if (!checkText(run.getText(0))) {continue;}//判断是否为chart并替换(这里也是将图表作为变量,然后使用jfreechart生成的图表替换掉)if (isContainsChart(run)) {handleChart(run, chartMap);continue;}//替换文本变量Object obj = matchValue(run.getText(0), textMap);if (obj instanceof String) {run.setText((String) obj, 0);}}}}}public static void handleTable(XWPFDocument xwpfDocument, Map<String, Object> tableMap) {// 获取文档中所有的表格List<XWPFTable> tables = xwpfDocument.getTables();for (int i = 0; i < tables.size(); i++) {XWPFTable table = tables.get(i);List<XWPFTableRow> rows = table.getRows();// 校验是否需要替换变量if (checkText(table.getText())) {for (XWPFTableRow row : rows) {List<XWPFTableCell> tableCells = row.getTableCells();for (XWPFTableCell tableCell : tableCells) {if (checkText(tableCell.getText())) {List<XWPFParagraph> paragraphs = tableCell.getParagraphs();for (XWPFParagraph paragraph : paragraphs) {List<XWPFRun> runs = paragraph.getRuns();for (XWPFRun run : runs) {run.setText(matchValue(tableCell.getText(), tableMap), 0);}}}}}}}}/*** @param text ${...} 带${}的变量text* @param map 存储需要替换的数据* @return 需要替换的变量值* @Description 有${}的值匹配出替换的数据,没有${}就返回原来的数据*/public static String matchValue(String text, Map<String, Object> map) {int n = text.length()-text.replaceAll("\\$", "").length();for (int i = 0; i < n; i++) {String key = text.substring(text.indexOf("$"), text.indexOf("}")+1);String key1 = String.valueOf(map.get(key.substring(2, key.indexOf("}"))));text = text.replace(key, StringUtils.isEmpty(key1) ? "--" : key1);}return text;}public static void handleChart(XWPFRun run, Map<String, Object> chartMap) throws IOException {//获取文本的值String text = run.getText(0);String value = text.substring(text.indexOf("$")+2, text.indexOf("}"));Map<String, Object> chartData = (Map<String, Object>) chartMap.get(value);// 图表数据为空,不生成图表if (null == chartData || CollectionUtils.isEmpty(chartData)) {run.setText("暂无数据", 0);return;}// 数据全部为0,则生成图表if (isAllZero(chartData)) {run.setText("暂无数据", 0);return;}String pictureUrl = "";InputStream inputStream = null;try {// 将此处变量设置为空run.setText("", 0);// 换行run.addBreak();if (text.indexOf("VerticalBarChart") != -1) {// 纵向柱状图// 使用jfreechart生成图表,并保存为图片,返回图片的pathpictureUrl = JfreeUtil.createBarChart("", chartData, "", "", PlotOrientation.VERTICAL, 700 ,400);inputStream=new FileInputStream(pictureUrl);// 添加图片run.addPicture(inputStream, XWPFDocument.PICTURE_TYPE_PNG, "", Units.toEMU(400), Units.toEMU(250));} else if (text.indexOf("HorizontalBarChart") != -1) {// 横向柱状图pictureUrl = JfreeUtil.createBarChart("", chartData, "", "", PlotOrientation.HORIZONTAL, 700 ,400);inputStream=new FileInputStream(pictureUrl);run.addPicture(inputStream, XWPFDocument.PICTURE_TYPE_PNG, "", Units.toEMU(400), Units.toEMU(250));} else if (text.indexOf("PieChart") != -1) {// 饼图Map<String, Double> doubleMap = new LinkedHashMap<>();chartData.forEach((k,v) -> doubleMap.put(k, Double.valueOf(v.toString())));pictureUrl = JfreeUtil.createPieChart("", doubleMap, 600, 500);inputStream=new FileInputStream(pictureUrl);run.addPicture(inputStream, XWPFDocument.PICTURE_TYPE_PNG, "", Units.toEMU(360), Units.toEMU(300));} else if (text.indexOf("LineChart") != -1) {// 折线图pictureUrl = JfreeUtil.createLineChart("", chartData, "", "", PlotOrientation.VERTICAL, 800 ,400);inputStream=new FileInputStream(pictureUrl);run.addPicture(inputStream, XWPFDocument.PICTURE_TYPE_PNG, "", Units.toEMU(400), Units.toEMU(200));}} catch (Exception e) {log.error("handle chart fail", e);throw new ConsciousException("创建图表失败!");} finally {if (inputStream != null) {inputStream.close();}File pictureFile = new File(pictureUrl);if(pictureFile.exists()){pictureFile.delete();}}}}

/*** 创建图集图表*/public class JfreeUtil {private static final Font FONT = new Font("宋体", Font.PLAIN, 12);public static String createPieChart(String title, Map<String, Double> datas, int width, int height) throws IOException {//根据jfree生成一个本地饼状图DefaultPieDataset pds = new DefaultPieDataset();datas.forEach(pds::setValue);//图标标题、数据集合、是否显示图例标识、是否显示tooltips、是否支持超链接JFreeChart chart = ChartFactory.createPieChart(title, pds, true, false, false);chart.getTitle().setFont(FONT);chart.getLegend().setItemFont(FONT);//设置抗锯齿chart.setTextAntiAlias(false);PiePlot plot = (PiePlot) chart.getPlot();plot.setStartAngle(90);plot.setNoDataMessage("暂无数据");plot.setNoDataMessagePaint(Color.blue); // 设置无数据时的信息显示颜色//忽略无值的分类plot.setIgnoreNullValues(true);plot.setIgnoreZeroValues(true);plot.setBackgroundAlpha(0f);//设置标签阴影颜色plot.setShadowPaint(new Color(255, 255, 255));//设置标签是否显示在饼块内部,默认时在外部plot.setSimpleLabels(true);chart.getLegend().setHorizontalAlignment(HorizontalAlignment.CENTER);//设置水平对齐 左对齐;chart.getLegend().setMargin(0, 0, 0, 0);//参数是:上,左,下,右. 设置饼图的位置chart.getLegend().setPadding(0, 0, 20, 0);// 设置饼图下文字的位置chart.getLegend().setFrame(new BlockBorder(0, 0, 0, 0));// 设置饼图下文字边框的位置// 图片中显示百分比:自定义方式,{0} 表示选项, {1} 表示数值, {2} 表示所占比例 ,小数点后两位NumberFormat percentInstance = NumberFormat.getPercentInstance();percentInstance.setRoundingMode(RoundingMode.HALF_UP);percentInstance.setMaximumFractionDigits(0);plot.setLabelGenerator(new StandardPieSectionLabelGenerator("{0},{2}", NumberFormat.getNumberInstance(), percentInstance));return createFile(chart, width, height);}public static String createBarChart(String title, Map<String, Object> datas, String type, String units, PlotOrientation orientation, int width, int height) throws IOException {//数据集DefaultCategoryDataset ds = new DefaultCategoryDataset();Iterator<Map.Entry<String, Object>> iterator = datas.entrySet().iterator();while (iterator.hasNext()) {Map.Entry<String, Object> entry = iterator.next();ds.setValue(NumberUtil.parseDouble(String.valueOf(entry.getValue())), "工单数量", StringUtils.defaultString(entry.getKey(), ""));}//创建柱状图,柱状图分水平显示和垂直显示两种JFreeChart chart = ChartFactory.createBarChart(title, type, units, ds, orientation, false, false, false);//设置文本抗锯齿,防止乱码chart.setTextAntiAlias(false);//得到绘图区CategoryPlot plot = (CategoryPlot) chart.getPlot();plot.setNoDataMessage("no data");//设置柱的透明度plot.setForegroundAlpha(1.0f);plot.setOutlineVisible(false);//获取X轴的对象CategoryAxis categoryAxis = plot.getDomainAxis();//坐标轴标尺值是否显示categoryAxis.setTickLabelsVisible(true);//坐标轴标尺是否显示categoryAxis.setTickMarksVisible(false);categoryAxis.setTickLabelFont(FONT);categoryAxis.setTickLabelPaint(Color.BLACK);categoryAxis.setLabelFont(FONT);// X轴标题//categoryAxis.setCategoryLabelPositionOffset(2);//图表横轴与标签的距离(10像素)//获取Y轴对象ValueAxis valueAxis = plot.getRangeAxis();valueAxis.setTickLabelsVisible(true);valueAxis.setTickMarksVisible(false);valueAxis.setUpperMargin(0.15);//设置最高的一个柱与图片顶端的距离(最高柱的20%)valueAxis.setLowerMargin(0d);valueAxis.setTickLabelFont(FONT);//Y轴数值valueAxis.setLabelPaint(Color.BLACK);//字体颜色valueAxis.setLabelFont(FONT);//Y轴标题NumberAxis numberAxis = (NumberAxis) plot.getRangeAxis();//设置Y轴刻度为整数numberAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());//设置网格横线颜色plot.setRangeGridlinePaint(Color.gray);plot.setRangeGridlinesVisible(true);//图片背景色plot.setBackgroundPaint(Color.white);plot.setOutlineVisible(false);// 设置原点xy轴相交,柱子从横轴开始,否则会有间隙plot.setAxisOffset(new RectangleInsets(0d, 0d, 0d, 0d));//设置网格横线大小plot.setDomainGridlineStroke(new BasicStroke(0.5F));plot.setRangeGridlineStroke(new BasicStroke(0.5F));//设置柱状图柱子相关CategoryPlot categoryPlot = chart.getCategoryPlot();BarRenderer rendererBar = (BarRenderer) categoryPlot.getRenderer();//组内柱子间隔为组宽的10%,调整柱子宽度rendererBar.setItemMargin(0.6);rendererBar.setMaximumBarWidth(0.07);rendererBar.setDrawBarOutline(true);rendererBar.setSeriesOutlinePaint(0, Color.decode("#4F97D5"));//设置柱的颜色#5B9BE6rendererBar.setSeriesPaint(0, Color.decode("#4F97D5"));//设置柱子上显示值rendererBar.setDefaultItemLabelGenerator(new StandardCategoryItemLabelGenerator());rendererBar.setDefaultItemLabelFont(FONT);rendererBar.setDefaultItemLabelsVisible(true);rendererBar.setDefaultItemLabelPaint(Color.BLACK);return createFile(chart, width, height);}public static String createLineChart(String title, Map<String, Object> datas, String type, String unit, PlotOrientation orientation, int width, int hight) throws IOException {DefaultCategoryDataset ds = new DefaultCategoryDataset();Iterator iterator = datas.entrySet().iterator();while (iterator.hasNext()) {Map.Entry entry = (Map.Entry) iterator.next();ds.setValue(NumberUtil.parseDouble(String.valueOf(entry.getValue())), "工单数量", entry.getKey().toString());}//创建折线图,折线图分水平显示和垂直显示两种JFreeChart chart = ChartFactory.createLineChart(title, type, unit, ds, orientation, false, true, true);//设置文本抗锯齿,防止乱码chart.setTextAntiAlias(false);//chart.setBorderVisible(true);//得到绘图区CategoryPlot plot = (CategoryPlot) chart.getPlot();//设置横轴标签项字体CategoryAxis categoryAxis = plot.getDomainAxis();categoryAxis.setCategoryLabelPositions(CategoryLabelPositions.UP_45);categoryAxis.setTickMarksVisible(false);categoryAxis.setTickLabelsVisible(true);categoryAxis.setTickLabelFont(new Font("宋体", Font.PLAIN, 12));categoryAxis.setLabelFont(new Font("宋体", Font.PLAIN, 12));ValueAxis valueAxis = plot.getRangeAxis();valueAxis.setTickMarksVisible(false);valueAxis.setTickLabelsVisible(true);valueAxis.setTickLabelFont(new Font("宋体", Font.PLAIN, 12));valueAxis.setLabelFont(new Font("宋体", Font.PLAIN, 12));NumberAxis numberAxis = (NumberAxis) plot.getRangeAxis();//设置Y轴刻度跨度numberAxis.setUpperMargin(0.15);numberAxis.setLowerMargin(0);numberAxis.setAutoRangeMinimumSize(5);// 设置背景透明度plot.setBackgroundAlpha(0.1f);plot.setForegroundAlpha(1.0f);// 设置网格横线颜色plot.setRangeGridlinePaint(Color.gray);// 设置网格横线大小plot.setDomainGridlineStroke(new BasicStroke(0.5F));plot.setRangeGridlineStroke(new BasicStroke(0.5F));plot.setBackgroundPaint(Color.white);plot.setOutlineVisible(false);// 设置原点xy轴相交,y轴为0时,点在横坐标上,否则不在横坐标上plot.setAxisOffset(new RectangleInsets(0d, 0d, 0d, 0d));// 生成折线图上的数字//绘图区域(红色矩形框的部分)LineAndShapeRenderer renderer = (LineAndShapeRenderer) plot.getRenderer();renderer.setDefaultItemLabelGenerator(new StandardCategoryItemLabelGenerator());//设置图表上的数字可见renderer.setDefaultItemLabelsVisible(true);//设置图表上的数字字体renderer.setDefaultItemLabelFont(new Font("宋体", Font.PLAIN, 12));// 设置线条是否被显示填充颜色renderer.setUseFillPaint(true);renderer.setSeriesStroke(0, new BasicStroke(4.0f));renderer.setSeriesPaint(0, Color.decode("#4472C4"));return createFile(chart, width, hight);}public static String createFile(JFreeChart chart, int width, int hight) throws IOException {File templateFile = File.createTempFile("jfreetemp", ".png");String filePath = templateFile.getParent() + File.separator + templateFile.getName();try {if (templateFile.exists()) {templateFile.delete();}ChartUtils.saveChartAsPNG(templateFile, chart, width, hight);} catch (IOException e) {log.error("create chart file fail.", e);throw new ConsciousException("创建图表文件失败!");}return filePath;}}

图表要注意的设置:(个人觉得是比较有用的)

//设置标签是否显示在饼块内部,默认时在外部

plot.setSimpleLabels(true);

//组内柱子间隔为组宽的10%,调整柱子宽度

rendererBar.setItemMargin(0.6);

rendererBar.setMaximumBarWidth(0.07);

// 设置原点xy轴相交,零点与坐标轴重合,

plot.setAxisOffset(new RectangleInsets(0d, 0d, 0d, 0d));

freemarker实现方式,首先获取到模板Template

public class FreeMarkerUtils {public static Template getTemplate(String templateName) throws IOException {// 设置FreeMarker的版本和编码格式Configuration configuration = new Configuration(Configuration.VERSION_2_3_30);configuration.setDefaultEncoding("utf-8");configuration.setClassicCompatible(true);// 设置FreeMarker生成Word文档所需要的模板的路径// 此处把模版文件都放在resources下的 templates 中configuration.setClassForTemplateLoading(FreeMarkerUtils.class, "/templates");configuration.setTemplateLoader(new ClassTemplateLoader(FreeMarkerUtils.class, "/templates"));// 设置FreeMarker生成Word文档所需要的模板return configuration.getTemplate(templateName, "utf-8");}}

然后填充数据即可

public static void main(String[] args) throws IOException {String fileName = "testdemo.docx";Map<String, String> dataMap = new HashMap<>();Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File(fileName)), StandardCharsets.UTF_8));Template template = FreeMarkerUtils.getTemplate("testdemo.ftl");// FreeMarker使用Word模板和数据生成Word文档try {template.process(dataMap, out);} catch (TemplateException e) {throw new RuntimeException(e);}out.flush();out.close();}

遗留问题:关于动态表格的处理,如何根据数据长度来动态调整表格的长度。貌似不能删除单元格和某一列,remove方法没用,只能置空。增加行、列后,单元格内的文本格式调整不生效,例如左对齐,居中等。欢迎评论区解答!

以上就是关于生成word的方法,大致描述了下每种的方法的思路,然后就是在每个过程中有一些注意点,还是比较重要的。也是第一次使用,有不对的地方希望大家多加指教,评论区指出,谢谢!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。