freemarker+Jfreechart生成Word文档(含图片)

Posted my_sunshine_y

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了freemarker+Jfreechart生成Word文档(含图片)相关的知识,希望对你有一定的参考价值。

     这几天再弄一个报表,要统计一些信息最终的部分展示结果如下:

基本工具freemarker,jfreechart

工程的部分结构如下


与生成Word有关的类主要有FreemarkerConfiguration和WordGenerator代码如下:

import com.bqs.ares.common.utils.CommonUtils;
import freemarker.template.Configuration;

import java.io.File;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Created by lenovo on 2016/9/27.
 */
public class FreemarkerConfiguration {
    private static Logger log = LoggerFactory.getLogger(FreemarkerConfiguration.class);
    private final static String filepath = "/freemarkerTemplate";
    private static Configuration configuration=null;
    public static Configuration getConfiguration(){
        if(configuration==null){
            configuration=new Configuration();
            try {
                configuration.setDirectoryForTemplateLoading(new File(CommonUtils.class.getResource(filepath).getFile()));
            }catch (Exception e){
                log.error(e.getMessage());
            }
        }
        return  configuration;
    }
}

import com.bqs.risk.dvp.common.PDFUtils.freemarker.FreemarkerConfiguration;
import freemarker.template.Configuration;
import freemarker.template.Template;

import java.io.*;
import java.util.Map;

/**
 * Created by lenovo on 2016/10/9.
 */
public class WordGenerator {
    /**
     * Generate html string.
     *
     * @param template   the name of freemarker teamlate.
     * @param variables  the data of teamlate.
     * @return htmlStr
     * @throws Exception
     */
    public static void generate(String template, Map<String,Object> variables, String htmlName) throws Exception{
        String basePath=HtmlGenerator.class.getResource("/").getPath()+"/freemarkerTemplate/word/";
        Configuration config = FreemarkerConfiguration.getConfiguration();
        config.setDefaultEncoding("UTF-8");
        Template tp = config.getTemplate(template);
        tp.setEncoding("UTF-8");

        String htmlPath=basePath+htmlName+".doc";
        File file = new File(htmlPath);
        if (!file.exists())
            file.createNewFile();
        Writer out = new BufferedWriter(new OutputStreamWriter(
                new FileOutputStream(file), "utf-8"));
        tp.process(variables, out);
        out.flush();
        out.close();
    }
}

用jfreeChart生成折线图和饼图的代码如下:

import com.bqs.risk.dvp.common.InfoPoint;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.labels.ItemLabelAnchor;
import org.jfree.chart.labels.ItemLabelPosition;
import org.jfree.chart.labels.StandardCategoryItemLabelGenerator;
import org.jfree.chart.labels.StandardPieSectionLabelGenerator;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PiePlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.renderer.category.LineAndShapeRenderer;
import org.jfree.chart.title.TextTitle;
import org.jfree.data.category.DefaultCategoryDataset;
import org.jfree.data.general.DefaultPieDataset;
import org.jfree.ui.TextAnchor;

import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.text.NumberFormat;
import java.util.*;
import java.util.List;

/**
 * Created by lenovo on 2016/9/28.
 */
public class JfreeChartUtils {
    private static Map<String,String> relationNameMap=null;
    private static DefaultCategoryDataset createDataset(int[] data) {
        DefaultCategoryDataset linedataset = new DefaultCategoryDataset();
        // 曲线名称
        String series = "时间-次数";  // series指的就是报表里的那条数据线
        //因此 对数据线的相关设置就需要联系到serise
        //比如说setSeriesPaint 设置数据线的颜色
        // 横轴名称(列名称)
        String[] time = new String[24];

        for (int i = 0; i < 24; i++) {
            time[i] = i + "";
        }
        //添加数据值
        for (int i = 0; i < data.length; i++) {
            linedataset.addValue(data[i],  //值
                    series,  //哪条数据线
                    time[i]); // 对应的横轴
        }
        return linedataset;

    }

    //生成事件统计图
    public static String createChart(String eventTypevalue, int[] data, String imageName) {
        String returnImagePath = "";
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        calendar.add(Calendar.MONTH, -1);
        if (data == null || data.length <= 0) {
            return null;
        }
        if (imageName == null || imageName.equals("")) {
            return null;
        }
        try {
            //定义图标对象
            JFreeChart chart = ChartFactory.createLineChart(null,// 报表题目,字符串类型
                    "时间", // 横轴
                    "次数", // 纵轴
                    createDataset(data), // 获得数据集
                    PlotOrientation.VERTICAL, // 图标方向垂直
                    false, // 显示图例
                    false, // 不用生成工具
                    false // 不用生成URL地址
            );
            chart.setTitle(calendar.get(Calendar.YEAR) + "年" + (calendar.get(Calendar.MONTH) + 1) + "月" + eventTypevalue + "统计 ");
            chart.setTitle(new TextTitle(chart.getTitle().getText(),new Font("宋体", 1, 13)));
            //整个大的框架属于chart  可以设置chart的背景颜色
            // 生成图形
            CategoryPlot plot = chart.getCategoryPlot();
            // 图像属性部分
            plot.setBackgroundPaint(Color.WHITE);
            plot.setDomainGridlinesVisible(true);  //设置背景网格线是否可见
            plot.setDomainGridlinePaint(Color.BLACK); //设置背景网格线颜色
            plot.setRangeGridlinePaint(Color.WHITE);
            plot.setNoDataMessage("没有数据");//没有数据时显示的文字说明。
            // 数据轴属性部分
            NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis();
            rangeAxis.setLabelFont(new Font("宋体", 1, 12));
            rangeAxis.setTickLabelFont((new Font("宋体", 1, 12)));
            rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
            rangeAxis.setAutoRangeIncludesZero(true); //自动生成
            rangeAxis.setUpperMargin(0.20);
            rangeAxis.setLabelAngle(Math.PI / 2.0);
            rangeAxis.setAutoRange(false);
            // 数据渲染部分 主要是对折线做操作
            LineAndShapeRenderer renderer = (LineAndShapeRenderer) plot.getRenderer();
            renderer.setBaseItemLabelsVisible(true);
            renderer.setSeriesPaint(0, Color.CYAN);    //设置折线的颜色
            renderer.setBaseShapesFilled(true);
            renderer.setBaseItemLabelsVisible(true);
            renderer.setBasePositiveItemLabelPosition(new ItemLabelPosition(ItemLabelAnchor.OUTSIDE12, TextAnchor.BASELINE_LEFT));
            renderer.setBaseItemLabelGenerator(new StandardCategoryItemLabelGenerator());
            renderer.setBaseItemLabelFont(new Font("Dialog", 1, 10));  //设置提示折点数据形状
            plot.setRenderer(renderer);
            // 创建文件输出流
            String imagePath = JfreeChartUtils.class.getResource("/").getPath() + "/freemarkerTemplate/images/" + imageName + ".jpg";
            File fos_jpg = new File(imagePath);
            if (!fos_jpg.exists()) {
                fos_jpg.createNewFile();
            }
            // 输出到哪个输出流
            ChartUtilities.saveChartAsJPEG(fos_jpg, chart, // 统计图表对象
                    1100, // 宽
                    400 // 高
            );
            returnImagePath = imagePath;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return returnImagePath;
    }


    /**
     * 用户设备关联图,每一张图有三条线
     *
     * @param titleName:
     * @param relatioContentList
     * @return 图片的地址
     */
    public static String createChart(String imageName, String titleName, Map<String, Map<String, String>> relatioContentList) {

        String imageFilePath = "";
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        calendar.add(Calendar.MONTH, -1);
        //定义图标对象
        try {
            JFreeChart chart = ChartFactory.createLineChart(null,// 报表题目,字符串类型
                    "数量", // 横轴
                    "计数", // 纵轴
                    createDataset(relatioContentList), // 获得数据集
                    PlotOrientation.VERTICAL, // 图标方向垂直
                    true, // 显示图例
                    false, // 不用生成工具
                    false // 不用生成URL地址
            );
            chart.setTitle(calendar.get(Calendar.YEAR) + "年" + (calendar.get(Calendar.MONTH) + 1) + "月" + titleName + "统计 ");
            chart.setTitle(new TextTitle(chart.getTitle().getText(),new Font("宋体", 1, 13)));
            //整个大的框架属于chart  可以设置chart的背景颜色
            // 生成图形
            CategoryPlot plot = chart.getCategoryPlot();
            // 图像属性部分
            plot.setBackgroundPaint(Color.WHITE);
            plot.setDomainGridlinesVisible(true);  //设置背景网格线是否可见
            plot.setDomainGridlinePaint(Color.BLACK); //设置背景网格线颜色
            plot.setRangeGridlinePaint(Color.WHITE);
            plot.setNoDataMessage("没有数据");//没有数据时显示的文字说明。
            // 数据轴属性部分
            NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis();
            rangeAxis.setLabelFont((new Font("宋体", 1, 12)));
            rangeAxis.setTickLabelFont((new Font("宋体", 1, 12)));
            rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
            rangeAxis.setAutoRangeIncludesZero(true); //自动生成
            rangeAxis.setUpperMargin(0.20);
            rangeAxis.setLabelAngle(Math.PI / 2.0);
            rangeAxis.setAutoRange(false);
            // 数据渲染部分 主要是对折线做操作
            LineAndShapeRenderer renderer = (LineAndShapeRenderer) plot.getRenderer();
            renderer.setBaseItemLabelsVisible(true);
            renderer.setSeriesPaint(0, Color.CYAN);    //设置折线的颜色
            renderer.setBaseShapesFilled(true);
            renderer.setBaseItemLabelsVisible(true);
            renderer.setBasePositiveItemLabelPosition(new ItemLabelPosition(ItemLabelAnchor.OUTSIDE12, TextAnchor.BASELINE_LEFT));
            renderer.setBaseItemLabelGenerator(new StandardCategoryItemLabelGenerator());
            renderer.setBaseItemLabelFont(new Font("Dialog", 1, 10));  //设置提示折点数据形状
            plot.setRenderer(renderer);
            // 创建文件输出流
            imageFilePath = JfreeChartUtils.class.getResource("/").getPath() + "/freemarkerTemplate/images/" + imageName + ".jpg";
            File fos_jpg = new File(imageFilePath);
            if (!fos_jpg.exists()) {
                fos_jpg.createNewFile();
            }
            // 输出到哪个输出流
            ChartUtilities.saveChartAsJPEG(fos_jpg, chart, // 统计图表对象
                    1100, // 宽
                    400 // 高
            );
        } catch (IOException e) {
            e.printStackTrace();
        }
        return imageFilePath;
    }

    private static DefaultCategoryDataset createDataset(Map<String, Map<String, String>> relatioContentList) {
        if(relationNameMap==null){
            relationNameMap=new HashMap<>();
           //此处省略
         
        }
        DefaultCategoryDataset linedataset = new DefaultCategoryDataset();
        for (Map.Entry<String, Map<String, String>> relation : relatioContentList.entrySet()) {
            //得到hbase中一列数据就是一条线
            String relationName =relationNameMap.get(relation.getKey());
            Map<String, String> relationContent = relation.getValue();

            List<Point> points = new ArrayList<>();//点
            for (Map.Entry<String, String> numCounts : relationContent.entrySet()) {
                int x = Integer.parseInt(numCounts.getKey());
                int y = Integer.parseInt(numCounts.getValue());
                Point p = new Point(x, y);
                points.add(p);
            }
            points.sort((p1, p2) -> p1.getX() - p2.getX());
            //添加数据值
            if (points != null && points.size() > 0) {
                for (Point p : points) {
                    linedataset.addValue(p.getY(),  //值
                            relationName,//哪条数据线
                            p.getX() + ""); // 对应的横轴
                }
            }
        }
        return linedataset;
    }

    public static List<InfoPoint> getTop20Points(Map<String, String> values) {
        if (values == null || values.size() < 0) {
            return null;
        }
        List<InfoPoint> pointList = new ArrayList<>();
        for (Map.Entry<String, String> value : values.entrySet()) {
            String info = value.getKey();
            int num = Integer.parseInt(value.getValue());
            InfoPoint infoPoint = new InfoPoint(info, num);
            pointList.add(infoPoint);
        }
        pointList.sort((p1, p2) -> p2.getNum() - p1.getNum());
        if (pointList.size() > 20) {
            for (int i = 20; i < pointList.size(); i++) {
                pointList.remove(i);
            }
        }
        return pointList;
    }
    //生成城市分布的饼图
    public static String creatLocationPieChart(String imageName, String typeInfo, Map<String, String> locationInfo) {
        if (locationInfo == null || locationInfo.size() <= 0) {
            return null;
        }
        String imageFilePath="";
        try {
            //设置饼图数据集
            DefaultPieDataset dataset = new DefaultPieDataset();
            List<InfoPoint> infoPoints=new ArrayList<>();
            for (Map.Entry<String, String> info : locationInfo.entrySet()) {
                String city = info.getKey();
                int num = Integer.parseInt(info.getValue());
                InfoPoint point=new InfoPoint();
                point.setInfo(city);
                point.setNum(num);
                infoPoints.add(point);
            }
            if(infoPoints!=null&&infoPoints.size()>8){
                infoPoints=infoPoints.subList(0,8);
            }
            for(InfoPoint infoPoint:infoPoints){
                dataset.setValue(infoPoint.getInfo(),infoPoint.getNum());
            }
            //通过工厂类生成JFreeChart对象
            JFreeChart chart = ChartFactory.createPieChart(typeInfo + "分布图", dataset, true, true, false);
            chart.setTitle(new TextTitle(chart.getTitle().getText(),new Font("宋体", 1, 13)));
            //加个副标题
            PiePlot pieplot = (PiePlot) chart.getPlot();
            pieplot.setLabelFont(new Font("宋体", 0, 11));

            //设置饼图是圆的(true),还是椭圆的(false);默认为true
            pieplot.setCircular(true);
            StandardPieSectionLabelGenerator standarPieIG = new StandardPieSectionLabelGenerator("{0}:({1},{2})", NumberFormat.getNumberInstance(), NumberFormat.getPercentInstance());
            pieplot.setLabelGenerator(standarPieIG);
            //没有数据的时候显示的内容
            pieplot.setNoDataMessage("无数据显示");
            pieplot.setLabelGap(0.02D);
            imageFilePath = JfreeChartUtils.class.getResource("/").getPath() + "/freemarkerTemplate/images/" + imageName + ".jpg";
            File fos_jpg = new File(imageFilePath);
            if (!fos_jpg.exists()) {
                fos_jpg.createNewFile();
            }
            // 输出到哪个输出流
            ChartUtilities.saveChartAsJPEG(fos_jpg, chart, // 统计图表对象
                    800, // 宽
                    800 // 高
            );
        }catch (Exception e){
            e.printStackTrace();
        }
        return imageFilePath;
    }
}

//折线中的点
class Point {
    private int x;
    private int y;

    public Point() {
    }

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
<span style="font-size:14px;">    }
}
</span>
图片得到之后就是生成Word了,基本思路是先用Word做好模板,另存为xml,然后使用freemarker标签来替换,最后改成ftl文件就是模板,图片也只是替换那一大堆编码而已
 <w:pict>
              <w:binData w:name="wordml://03000003${imagePath_index}.png" xml:space="preserve">${imagePath}</w:binData>
                <v:shape id="_x0000_i1028" type="#_x0000_t75" style="width:440pt;height:440pt">
                    <v:imagedata src="wordml://03000003${imagePath_index}.png" o:title="6350.tmp"/>
                </v:shape>
            </w:pict>

但这里确实花了我很多时间,主要的坑如下:

     1.freemarker标签的使用:尤其要注意是否为空的情况所以对于list还是map最好都加上这一句:

<#if infoLocationList??&&infoLocationList?size gt 0>
是否存在,至于具体的循环遍历网上有不再赘述

     2.Word模板另存为xml文件时一定要注意,如果是较高一点的版本有两种格式xml和2003xml这个在处理图片时有较大差别,我是使用2003xml版本(高版本的在处理图片时有错误),它在处理的时候需要将图片转成base64码具体代码如下:

 private String getImageStr(String imagePath) {
        String imgFile = imagePath;
        InputStream in = null;
        byte[] data = null;
        try {
            in = new FileInputStream(imgFile);
            data = new byte[in.available()];
            in.read(data);
            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        BASE64Encoder encoder = new BASE64Encoder();
        return encoder.encode(data);
    }

  3.多张图片插入时会遇到图片重复记得改name和src,从List中取出数据并且实现插入多张图片:

<w:p wsp:rsidR="00C46D75" wsp:rsidRDefault="00C46D75" wsp:rsidP="00AC571C"/>
        <#if infoLocationList??&&infoLocationList?size gt 0>
            <w:p wsp:rsidR="0051625F" wsp:rsidRDefault="007F46BC" wsp:rsidP="00AC571C">
          <w:r>
            <w:rPr>
              <w:rFonts w:hint="fareast"/>
              <wx:font wx:val="宋体"/>
            </w:rPr>
            <w:t>半年内手机号、身份证号、IP、GPS归属地信息分布</w:t>
          </w:r>
        </w:p>
            <#list infoLocationList as imagePath>
                <w:p wsp:rsidR="007F46BC" wsp:rsidRDefault="00E75FA4" wsp:rsidP="00AC571C">
          <w:r>
            <w:tab/>
          </w:r>
          <w:r wsp:rsidR="00946545" wsp:rsidRPr="00946545">
            <w:rPr>
              <w:noProof/>
            </w:rPr>
            <w:pict>
              <w:binData w:name="wordml://03000003${imagePath_index}.png" xml:space="preserve">${imagePath}</w:binData>
                <v:shape id="_x0000_i1028" type="#_x0000_t75" style="width:440pt;height:440pt">
                    <v:imagedata src="wordml://03000003${imagePath_index}.png" o:title="6350.tmp"/>
                </v:shape>
            </w:pict>
          </w:r>
        </w:p>
            </#list>
        </#if>
  4.如果模板太大转成的xml文件太大,看着是密密麻麻的一大片根本无法进一步处理,建议使用IDE进行代码格式化,想eclipse等等一般都可以,如果发现工具也无法格式化可以直接百度xml格式化,然后会有在线的工具

  5.有时候会遇到成功生成Word文档但是无法打开的情况,这时候可以根据错误提示用文本编辑工具来具体到哪一行去看看,如果遇到什么结束元素标签名称与开始标签名称不匹配多半是ftl中标签配对有问题(这个有时候即使没有改过也会出错,所以最好借助工具好好补齐标签)

 6.乱码问题,主要是在Linux服务器上会出现,具体方法网上也有

以上是自己的一些经验之谈,更多是在整个流程以及自己所遇到的坑上,具体说每一步怎么处理网上都会有

 

以上是关于freemarker+Jfreechart生成Word文档(含图片)的主要内容,如果未能解决你的问题,请参考以下文章

如何利用jfreechart生成柱状,饼状,折线形图形。

JFreeChart 详解

java根据模板HTML动态生成PDF

JFreeChart应用(生成折线图)

如何使用javabean汇入jfreechart生成的图表

如何使用Jfreechart生成柱状图?