FreeMarker+JFreeChart生成包含图表图片的word文档

本文介绍了如何结合Freemarker模板和JFreeChart库生成包含图表的Word文档。首先,创建一个包含文字和图片的模板文档,并将需要替换的内容用`${xxx}`标记。然后,将模板另存为XML格式并修改图片部分,用变量替换图片编码。接着,将XML文件转换为FTL模板,并放入项目资源文件夹。最后,编写`ExportWordUtils`工具类,提供生成图表和导出Word文档的功能。测试代码`Demo.java`展示了调用这些功能的方法。
  • 一、制作模板

1、创建模板word文档

创建类似《demo.docx》的模板文档,其中包含文字内容、图片内容,特别注意:图片必须贴上内容,并且如果需要多个图片的话不能使用同一张图片,如果不贴图片或者使用同一张图片的话后边的.ftl文件无法修改。

2、另存为word xml格式文件,打开文件将需要填充内容的地方用“${xxx}”补充,图片部分先不动,如demo.xml示例。

3、把demo.xml文件后缀名改为“.ftl”,用notepad++打开demo.ftl,对其中图片部分进行修改,将图片的编码部分删掉改为对应的“${xxx}”变量。特别注意:多个图片是,该文件中图片的顺序有可能是乱的,应按照“<pkg:part”中的imageX的编号顺序来对应。

这里例子中有三个图片,替换完后如下图所示:

4、将demo.ftl文件放到项目的resources文件夹下,也可在resources文件夹下创建新的文件夹来存放模板文件,但是对应的代码中要修改模板路径。

  • 二、编写代码

ExportWordUtils.java为导出word文档的工具类,里边提供了生成饼图、柱状图、折线图,以及导出word的方法,调用方式参照TestDemo.java。

1、pom文件引入的包

<dependencies>
        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.28</version>
        </dependency>
        <dependency>
            <groupId>jfree</groupId>
            <artifactId>jfreechart</artifactId>
            <version>1.0.13</version>
        </dependency>

        <dependency>
            <groupId>jfree</groupId>
            <artifactId>jcommon</artifactId>
            <version>1.0.16</version>
        </dependency>
    </dependencies>

2、工具类ExportWordUtils.java

package org.example.demo;

import freemarker.core.ParseException;
import freemarker.log.Logger;
import freemarker.template.*;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.CategoryAxis;
import org.jfree.chart.axis.CategoryLabelPositions;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.labels.StandardCategoryItemLabelGenerator;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PiePlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.renderer.category.BarRenderer;
import org.jfree.chart.renderer.category.LineAndShapeRenderer;
import org.jfree.chart.title.TextTitle;
import org.jfree.data.category.CategoryDataset;
import org.jfree.data.general.PieDataset;
import sun.misc.BASE64Encoder;

import java.awt.*;
import java.io.*;
import java.text.DecimalFormat;
import java.util.Map;

/**
 * 导出Word文档(含图表)工具类
 * freemarker+jfreechart
 */
public class ExportWordUtils {
    private Configuration config = null;//freemarker配置对象

    //日志对象,使用时可以替换成自己的日志打印方式
    private Logger log = Logger.getLogger(ExportWordUtils.class.toString());

    //字体
    private Font titleFont = new Font("黑体", Font.BOLD,20);//图表标题字体
    private Font labelFont = new Font("宋体",Font.HANGING_BASELINE,12);//图表纵、横轴标题字体
    private Font tickLabelFont = new Font("宋体",Font.PLAIN,11);//图表纵、横轴内容字体
    private Font legendFont = new Font("宋体",Font.HANGING_BASELINE,12);//图表图例字体

    /**
     * 构造函数,初始化一些freemarker基本配置
     */
    public ExportWordUtils(){
        config = new Configuration(Configuration.VERSION_2_3_28);//freemark版本号
        config.setDefaultEncoding("utf-8");//编码格式,这里是utf-8
    }

    /**
     * freemarker生成word
     * @param dataMap 数据
     * @param wordName 要生成的文件名
     * @param saveFilePath 文件存放路径(不包含文件名,但包含/)
     * @param ftlName 模板文件名(这里必须是.ftl文件)
     * @param ftlPath 模板路径(不包含文件名,但包含/)
     */
    public void createWord(Map<String, Object> dataMap, String wordName, String saveFilePath, String ftlName, String ftlPath){
        //加载模板(路径)数据
        config.setClassForTemplateLoading(this.getClass(), ftlPath);
        //设置异常处理器 这样的话 即使没有属性也不会出错 如:${list.name}...不会报错
        config.setTemplateExceptionHandler(TemplateExceptionHandler.IGNORE_HANDLER);
        Template template = null;
        //截取模板文件名(去掉后缀)
        if(ftlName.endsWith(".ftl")) {
            ftlName = ftlName.substring(0, ftlName.indexOf(".ftl"));
        }
        //加载模板
        try {
            template = config.getTemplate(ftlName + ".ftl");
        } catch (TemplateNotFoundException e) {
            log.error("模板文件未找到", e);
            e.printStackTrace();
        } catch (MalformedTemplateNameException e) {
            log.error("模板类型不正确", e);
            e.printStackTrace();
        } catch (ParseException e) {
            log.error("解析模板出错,请检查模板格式", e);
            e.printStackTrace();
        } catch (IOException e) {
            log.error("IO读取失败", e);
            e.printStackTrace();
        }
        //创建目标文件
        File outFile = new File(saveFilePath+wordName);
        if(!outFile.getParentFile().exists()) {
            outFile.getParentFile().mkdirs();
        }
        Writer out = null;
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(outFile);
        } catch (FileNotFoundException e) {
            log.error("输出文件时未找到文件", e);
            e.printStackTrace();
        }
        out = new BufferedWriter(new OutputStreamWriter(fos));
        //将模板中的预先的代码替换为数据
        try {
            template.process(dataMap, out);
        } catch (TemplateException e) {
            log.error("填充模板时异常", e);
            e.printStackTrace();
        } catch (IOException e) {
            log.error("IO读取时异常", e);
            e.printStackTrace();
        }
        log.info("由模板文件:" + ftlPath+ftlName + ".ftl" + " 生成文件 :" + saveFilePath+wordName + " 成功!!");
        try {
            out.close();//web项目不可关闭
        } catch (IOException e) {
            log.error("关闭Write对象出错", e);
            e.printStackTrace();
        }
    }

    /**
     * 获得图片的Base64编码
     * @param imgFile 图片文件路径(包含文件名)
     * @return Base64编码
     */
    public String getImageStr(String imgFile) {
        InputStream in = null;
        byte[] data = null;
        try {
            in = new FileInputStream(imgFile);
        } catch (FileNotFoundException e) {
            log.error("加载图片未找到", e);
            e.printStackTrace();
        }
        try {
            data = new byte[in.available()];
            //注:FileInputStream.available()方法可以从输入流中阻断由下一个方法调用这个输入流中读取的剩余字节数
            in.read(data);
            in.close();
        } catch (IOException e) {
            log.error("IO操作图片错误", e);
            e.printStackTrace();
        }
        BASE64Encoder encoder = new BASE64Encoder();
        return encoder.encode(data);
    }

    /**
     * 从JFreeChart生成的图表获取Base64编码
     * @param chart JFreeChart生成的图表
     * @return 图表的Base64编码
     */
    public String getChartStr(JFreeChart chart){
        BASE64Encoder BASE64 = new BASE64Encoder();
        ByteArrayOutputStream bas = new ByteArrayOutputStream();
        try {
            //以png格式写入字节输出流
            ChartUtilities.writeChartAsPNG(bas,chart,1000,800);
            bas.flush();
            bas.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        //从上一步转换的输出流读入字节数组
        byte[] byteArray = bas.toByteArray();
        InputStream is = new ByteArrayInputStream(byteArray);
        try {
            byteArray = new byte[is.available()];
            is.read(byteArray);
            is.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        //字节数组转为Base64编码
        String xml_img = BASE64.encode(byteArray);
        return xml_img;
    }

    /**
     * JFreeChart创建饼图
     * @param title 标题
     * @param dataset 数据集合
     * @return 饼图对象
     */
    public JFreeChart createPieChart(String title, PieDataset dataset){
        JFreeChart chart = ChartFactory.createPieChart(title,dataset, true, false, false);
        chart.setTitle(new TextTitle(title, titleFont));//设置标题字体,避免中文白框

//        Font plotFont = new Font("宋体",Font.BOLD,12);
        PiePlot plot = (PiePlot) chart.getPlot();
        plot.setLabelFont(labelFont);//设置内容字体,避免中文白框
        chart.getLegend(0).setItemFont(legendFont);//设置图例内容字体,避免中文白框
        return chart;
    }

    /**
     * 生成柱状图
     * @param dataset 数据集
     * @param xName x轴的说明(如种类,时间等)
     * @param yName y轴的说明(如速度,时间等)
     * @param title 图标题
     * @return 柱状图对象
     */
    public JFreeChart createBarChart(CategoryDataset dataset, String xName,String yName, String title) {
        JFreeChart chart = ChartFactory.createBarChart(title, // 图表标题
                xName, // 目录轴的显示标签
                yName, // 数值轴的显示标签
                dataset, // 数据集
                PlotOrientation.VERTICAL, // 图表方向:水平、垂直
                true, // 是否显示图例(对于简单的柱状图必须是false)
                false, // 是否生成工具
                false // 是否生成URL链接
        );
        chart.setTitle(new TextTitle(title, titleFont));
//        Font labelFont = new Font("SansSerif", Font.TRUETYPE_FONT, 12);
        /*
         * VALUE_TEXT_ANTIALIAS_OFF表示将文字的抗锯齿关闭,
         * 使用的关闭抗锯齿后,字体尽量选择12到14号的宋体字,这样文字最清晰好看
         */
        // chart.getRenderingHints().put(RenderingHints.KEY_TEXT_ANTIALIASING,RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
        chart.setTextAntiAlias(false);
        chart.setBackgroundPaint(Color.white);
        // create plot  获取图表区域对象
        CategoryPlot plot = chart.getCategoryPlot();
        // 设置横虚线可见
        plot.setRangeGridlinesVisible(true);
        // 虚线色彩
        plot.setRangeGridlinePaint(Color.gray);

        // 数据轴精度
        NumberAxis vn = (NumberAxis) plot.getRangeAxis();
        // vn.setAutoRangeIncludesZero(true);
        DecimalFormat df = new DecimalFormat("#0.00");
        vn.setNumberFormatOverride(df); // 数据轴数据标签的显示格式
        // x轴设置
        CategoryAxis domainAxis = plot.getDomainAxis();
        domainAxis.setLabelFont(labelFont);//设置横轴标题字体,避免中文白框
        domainAxis.setTickLabelFont(tickLabelFont);//设置横轴内容字体,避免中文白框

        // Lable(Math.PI/3.0)度倾斜
        // domainAxis.setCategoryLabelPositions(CategoryLabelPositions
        // .createUpRotationLabelPositions(Math.PI / 3.0));

        domainAxis.setMaximumCategoryLabelWidthRatio(0.6f);// 横轴上的 Lable 是否完整显示

        // 设置距离图片左端距离
        domainAxis.setLowerMargin(0.1);
        // 设置距离图片右端距离
        domainAxis.setUpperMargin(0.1);
        // 设置 columnKey 是否间隔显示
        // domainAxis.setSkipCategoryLabelsToFit(true);

        plot.setDomainAxis(domainAxis);
        // 设置柱图背景色(注意,系统取色的时候要使用16位的模式来查看颜色编码,这样比较准确)
        plot.setBackgroundPaint(new Color(255, 255, 204));

        // y轴设置
        ValueAxis rangeAxis = plot.getRangeAxis();
        rangeAxis.setLabelFont(labelFont);//设置横轴标题字体,避免中文白框
        rangeAxis.setTickLabelFont(tickLabelFont);//设置纵轴内容字体,避免中文白框
        // 设置最高的一个 Item 与图片顶端的距离
        rangeAxis.setUpperMargin(0.15);
        // 设置最低的一个 Item 与图片底端的距离
        rangeAxis.setLowerMargin(0.15);
        plot.setRangeAxis(rangeAxis);

        chart.getLegend(0).setItemFont(legendFont);//设置图例字体,避免中文白框

        BarRenderer renderer = new BarRenderer();
        // 设置柱子宽度
        renderer.setMaximumBarWidth(0.05);
        // 设置柱子高度
        renderer.setMinimumBarLength(0.2);
        // 设置柱子边框颜色
//        renderer.setDefaultOutlinePaint(Color.BLACK);
        renderer.setBaseOutlinePaint(Color.BLACK);
        // 设置柱子边框可见
        renderer.setDrawBarOutline(true);

        // // 设置柱的颜色
        renderer.setSeriesPaint(0, new Color(204, 255, 255));
        renderer.setSeriesPaint(1, new Color(153, 204, 255));
        renderer.setSeriesPaint(2, new Color(51, 204, 204));

        // 设置每个地区所包含的平行柱的之间距离
        renderer.setItemMargin(0.0);

        // 显示每个柱的数值,并修改该数值的字体属性
        renderer.setIncludeBaseInRange(true);
//        renderer.setDefaultItemLabelGenerator(new StandardCategoryItemLabelGenerator());
//        renderer.setDefaultItemLabelsVisible(true);

        renderer.setBaseItemLabelGenerator(new StandardCategoryItemLabelGenerator());
        renderer.setBaseItemLabelsVisible(true);

        plot.setRenderer(renderer);
        // 设置柱的透明度
        plot.setForegroundAlpha(1.0f);
        return chart;
    }

    /**
     * 生成折线图
     * @param dataset 数据集
     * @param xName x轴说明
     * @param yName y轴说明
     * @param title 图表标题
     * @return 折线图对象
     */
    public JFreeChart createLineChart(CategoryDataset dataset,String xName,String yName,String title){
        JFreeChart chart = ChartFactory.createLineChart(title, xName, yName,
                dataset, PlotOrientation.VERTICAL, true, true, false);
        //设置标题
        chart.setTitle(new TextTitle(title, titleFont));
//        // 设置面板字体
//        Font labelFont = new Font("宋体", Font.PLAIN, 15);
//        Font tickLabelFont = new Font("宋体", Font.PLAIN, 15);

        chart.setTextAntiAlias(false);
        chart.setBackgroundPaint(Color.WHITE);

        CategoryPlot categoryplot = (CategoryPlot) chart.getPlot();
        // x轴 // 分类轴网格是否可见
        categoryplot.setDomainGridlinesVisible(true);
        // y轴 //数据轴网格是否可见
        categoryplot.setRangeGridlinesVisible(true);

        categoryplot.setRangeGridlinePaint(Color.WHITE);// 虚线色彩

        categoryplot.setDomainGridlinePaint(Color.WHITE);// 虚线色彩

        categoryplot.setBackgroundPaint(Color.lightGray);

        // 设置轴和面板之间的距离
        // categoryplot.setAxisOffset(new RectangleInsets(5D, 5D, 5D, 5D));

        CategoryAxis domainAxis = categoryplot.getDomainAxis();
        domainAxis.setLabelFont(labelFont);//设置横轴标题字体,避免中文白框
        domainAxis.setTickLabelFont(tickLabelFont);//设置横轴内容字体,避免中文白框
//        domainAxis.setCategoryLabelPositions(CategoryLabelPositions.UP_45); // 横轴上的 Lable 45度倾斜

        ValueAxis rangeAxis = categoryplot.getRangeAxis();
        rangeAxis.setLabelFont(labelFont);//设置纵轴标题字体,避免中文白框
        rangeAxis.setTickLabelFont(tickLabelFont);//设置纵轴内容字体,避免中文白框

        chart.getLegend(0).setItemFont(legendFont);//设置图例字体,避免中文白框

        // 设置距离图片左端距离
        domainAxis.setLowerMargin(0.0);
        // 设置距离图片右端距离
        domainAxis.setUpperMargin(0.0);

        NumberAxis numberaxis = (NumberAxis) categoryplot.getRangeAxis();
        numberaxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
        numberaxis.setAutoRangeIncludesZero(true);

        // 获得renderer 注意这里是下嗍造型到lineandshaperenderer!!
        LineAndShapeRenderer lineandshaperenderer = (LineAndShapeRenderer) categoryplot
                .getRenderer();

//        lineandshaperenderer.setDefaultLinesVisible(true); // series 点(即数据点)可见
//        lineandshaperenderer.setDefaultLinesVisible(true); // series 点(即数据点)间有连线可见
        // 显示折点数据
         lineandshaperenderer.setBaseItemLabelGenerator(new StandardCategoryItemLabelGenerator());
         lineandshaperenderer.setBaseItemLabelsVisible(true);

         return chart;
    }

}

3、测试代码Demo.java

package org.example.demo;

import org.jfree.chart.JFreeChart;
import org.jfree.data.category.CategoryDataset;
import org.jfree.data.category.DefaultCategoryDataset;
import org.jfree.data.general.DefaultPieDataset;

import java.util.HashMap;
import java.util.Map;

public class TestDemo {
    public static void main(String[] args) {
        ExportWordUtils wordUtils = new ExportWordUtils();
        //饼图数据集
        DefaultPieDataset dataset = new DefaultPieDataset();
        dataset.setValue(" 市场前期", new Double(10));
        dataset.setValue(" 立项", new Double(15));
        dataset.setValue(" 计划", new Double(10));
        dataset.setValue(" 需求与设计", new Double(10));
        dataset.setValue(" 执行控制", new Double(35));
        dataset.setValue(" 收尾", new Double(10));
        dataset.setValue(" 运维",new Double(10));
        //生成饼图
        JFreeChart pieChart = wordUtils.createPieChart("测试",dataset);
        String pieStr = wordUtils.getChartStr(pieChart);


        //柱状图、折线图数据集
        DefaultCategoryDataset cgDataset = new DefaultCategoryDataset();
        cgDataset.addValue( 0.21 , "苹果" , "北京" );
        cgDataset.addValue( 0.66 , "苹果" , "上海" );
        cgDataset.addValue( 0.23 , "苹果" , "广州" );
        cgDataset.addValue( 0.40 , "苹果" , "成都" );
        cgDataset.addValue( 0.26 , "苹果" , "深圳" );

        cgDataset.addValue( 0.25 , "梨子" , "北京" );
        cgDataset.addValue( 0.21 , "梨子" , "上海" );
        cgDataset.addValue( 0.10 , "梨子" , "广州" );
        cgDataset.addValue( 0.40 , "梨子" , "成都" );
        cgDataset.addValue( 0.16 , "梨子" , "深圳" );

        //生成柱状图
        JFreeChart cgChart = wordUtils.createBarChart(cgDataset,"城市","数据","测试柱状图");
        String cgStr = wordUtils.getChartStr(cgChart);


        //生成折线图
        JFreeChart lineChart = wordUtils.createLineChart(cgDataset,"x轴","y轴","测试折线图");
        String lineStr = wordUtils.getChartStr(lineChart);

        //生成word文件
        Map<String, Object> dataMap = new HashMap<String, Object>();
        dataMap.put("name", "测试");
        dataMap.put("age", 26);
        dataMap.put("telphone", "123456789101");
        dataMap.put("piename","饼状图");
        dataMap.put("pieimg", pieStr);
        dataMap.put("cgname","柱状图");
        dataMap.put("cgimg",cgStr);
        dataMap.put("linename","折线图");
        dataMap.put("lineimg",lineStr);

        wordUtils.createWord(dataMap,"生成报告测试.doc","E:/","demo.ftl","/");
    }
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值