POI excel插入图表

POI excel插入图表

据我所知POI插入表格好像没有对应的API,所以想到一个折中的方法:借用JFreeChart生成表格之后转化为图片,进而插入到excel中。

首先定义接口

public interface ChartToImgMaker {

    //用于配置图表标题,作为trans()的第一个参数option的key
    String TITLE_KEY = "titleKey";

    //用于配置图表X轴名称,作为trans()的第一个参数option的key
    String X_AXIS_KEY = "xAxisKey";

    //用于配置图表Y轴名称,作为trans()的第一个参数option的key
    String Y_AXIS_KEY = "yAxisKey";

    String trans(Map option, Map data, String imgPath);
    /**
     * @param option 配置信息
     * @param data  原始数据
     * @param imgPath  生成后的图像保存路径
     * @param width     图像的宽
     * @param height    图像的高
     * @return
     */
    String trans(Map option, Map data, String imgPath, int width, int height);

}

接下来给出骨架实现
主要固定转换流程:createDataset-> createChart ->transToImg

public abstract class AbstractChartToImgMaker implements ChartToImgMaker{

    //创建数据
    protected abstract Dataset createDataset(Map data);

    protected abstract JFreeChart createChart(Map option, Dataset dataset);

    /**
     * 设置图表主题,用于解决中文乱码
     * @return
     */
    protected ChartTheme setStandardChartThem() {
        //创建主题样式
        StandardChartTheme standardChartTheme=new StandardChartTheme("CN");
        //设置标题字体
        standardChartTheme.setExtraLargeFont(new Font("隶书",Font.BOLD,20));
        //设置图例的字体
        standardChartTheme.setRegularFont(new Font("宋书",Font.PLAIN,15));
        //设置轴向的字体
        standardChartTheme.setLargeFont(new Font("宋书", Font.PLAIN,15));
        //应用主题样式
        ChartFactory.setChartTheme(standardChartTheme);
        return standardChartTheme;
    }

    //转换的底层方法,因为固定,所以定义为final方法
    final protected String transToImg(JFreeChart chart, String imgPath, int width, int height) {
        try {
            OutputStream os = new FileOutputStream(imgPath);
            ChartUtilities.writeChartAsJPEG(os, chart, width, height);
            os.close();
            return imgPath;
        } catch (IOException e) {
            e.printStackTrace();
            return "";
        }
    }

    @Override
    final public String trans(Map option, Map data, String imgPath) {
        return trans(option, data, imgPath, 500, 500);
    }

    //暴露接口的实现,因为流程固定所以定义为final方法
    @Override
    final public String trans(Map option, Map data, String imgPath, int width, int height) {
        Dataset dataset = createDataset(data);
        setStandardChartThem();
        JFreeChart jFreeChart = createChart(option, dataset);
        return transToImg(jFreeChart, imgPath, width, height);
    }

}

为折线图和柱状图给出默认实现

public class CategoryChartToImgMaker extends AbstractChartToImgMaker {


    //用于配置X轴的类目列表, 作为trans()的第一个参数option的key
    public static final String SERIES_KEY_LIST = "series_key_list";

    //用于配置类目型表格的具体类型, 作为trans()的第一个参数option的key
    public static final String CATEGORY_CHART_TYPE_KEY = "categoryChartType";

    /**
     * CATEGORY_CHART_TYPE_* 的常量值指定生成表格的创建方法名,
     * 作为trans()的第一个参数option的key为 CATEGORY_CHART_TYPE_KEY 的value值
     */
    public static final String CATEGORY_CHART_TYPE_LINE = "createLineChart";

    public static final String CATEGORY_CHART_TYPE_LINE_3D = "createLineChart3D";

    public static final String CATEGORY_CHART_TYPE_BAR = "createBarChart";

    public static final String CATEGORY_CHART_TYPE_BAR_3D = "createBarChart3D";


    protected CategoryItemLabelGenerator itemLabelGenerator;

    public CategoryChartToImgMaker() {
        super();
    }

    public CategoryChartToImgMaker(CategoryItemLabelGenerator itemLabelGenerator) {
        this.itemLabelGenerator = itemLabelGenerator;
    }

    /**
     * 组装数据的方法,此处给出默认实现,所以有固定的数据可以要求,如果觉得数据结构麻烦可以自己重写此方法即可
     * 组装数据, data数据格式要求:
     * 1. 必须添加key为LineChartToImgMaker.SERIES_KEY_LIST的value;value的类型必须为List
     * 此项数据作为X轴类目数据
     * 2. data 中其他key代表一个系列数据,value的数据类型必须为List
     * 某项系列的value 值对应的list的size必须和X轴数据的size相等,并且一一对应
     *
     * @param data
     * @return
     */
    @Override
    protected Dataset createDataset(Map data) {
        DefaultCategoryDataset lineDataset = new DefaultCategoryDataset();
        List xAxisData = (List) data.get(SERIES_KEY_LIST);
        /*一种针对更为简单的数据格式的组装数据实现,由于需要多层循环放弃使用
        List> seriesData;
        for (Map.Entry entry : data.entrySet()) {
            seriesData = (List>) entry.getValue();
            for (Map value : seriesData) {
                for (Map.Entry valueEntry : value.entrySet()) {
                    lineDataset.addValue(valueEntry.getValue(), entry.getKey(), valueEntry.getKey());
                }
            }
        }*/
        List seriesData;
        Number value;
        for (Map.Entry entry : data.entrySet()) {
            if (SERIES_KEY_LIST.equals(entry.getKey())) {
                continue;
            }
            seriesData = (List) entry.getValue();
            for (int index = 0, length = seriesData.size(); index < length; index++) {
                value = seriesData.get(index);
                lineDataset.addValue(value == null ? 0 : value, entry.getKey(), xAxisData.get(index));
            }
        }
        return lineDataset;
    }


    @Override
    protected JFreeChart createChart(Map option, Dataset dataset) {
        DefaultCategoryDataset categoryDataset = (DefaultCategoryDataset) dataset;

        //获取配置信息中要创建的表格类型,从而决定调用的创建方法
        String createMethodName = option.get(CATEGORY_CHART_TYPE_KEY);

        JFreeChart chart;
        try {
            //通过方法名获取创建图表的方法
            Method method = ChartFactory.class.getMethod(createMethodName, new Class[]{
                    String.class, String.class, String.class,
                    CategoryDataset.class, PlotOrientation.class,
                    boolean.class, boolean.class, boolean.class}
            );
            chart = (JFreeChart) method.invoke(null, option.get(TITLE_KEY), option.get(X_AXIS_KEY), option.get(Y_AXIS_KEY),
                    categoryDataset, PlotOrientation.VERTICAL, true, false, false);
        } catch (Exception e) {
            //没有找到合适的创建方法,则默认创建折线图表
            chart = ChartFactory.createLineChart(option.get(TITLE_KEY), option.get(X_AXIS_KEY), option.get(Y_AXIS_KEY),
                    categoryDataset, PlotOrientation.VERTICAL, true, false, false);
        }

        CategoryPlot plot = chart.getCategoryPlot();
        plot.setBackgroundPaint(Color.white);
        plot.setDomainGridlinesVisible(true);  //设置背景网格线是否可见
        plot.setDomainGridlinePaint(Color.BLACK); //设置背景网格线颜色
        plot.setRangeGridlinePaint(Color.GRAY);
        plot.setNoDataMessage("无数据");
        renderChartAsDefault(plot);
        return chart;

    }

    private void renderChartAsDefault(CategoryPlot plot) {
        CategoryItemRenderer categoryItemRenderer = plot.getRenderer();
        if (itemLabelGenerator == null) {
            itemLabelGenerator = new StandardCategoryItemLabelGenerator();
        }
        categoryItemRenderer.setBaseItemLabelGenerator(itemLabelGenerator);
        categoryItemRenderer.setBaseItemLabelsVisible(true);
        categoryItemRenderer.setBasePositiveItemLabelPosition(new ItemLabelPosition(ItemLabelAnchor.OUTSIDE12, TextAnchor.BASELINE_LEFT));
        categoryItemRenderer.setBaseItemLabelFont(new Font("Dialog", 1, 14));
        plot.setRenderer(categoryItemRenderer);
    }

    //主要用于设置自定义的标识
    public void setItemLabelGenerator(CategoryItemLabelGenerator itemLabelGenerator) {
        this.itemLabelGenerator = itemLabelGenerator;
    }

}

CategoryChartToImgMaker 主要用于创建折线图、柱状图等多类目的图表,通过给出的配置信息来调用不同的构造方法获取不同的图表。为了说明问题,只给给出了4类图表支持,通过该类的前缀为CATEGORY_CHART_TYPE_*静态字段来确定创建什么类型的图表,该字段的值其实就是JFreeChar提供的ChartFactory的各种创建图表方法名(因为要通过反射获取该方法)。

示例

   Map option = new HashMap();


       //配置生成的图表类型,示例为折线图
       option.put(CategoryChartToImgMaker.CATEGORY_CHART_TYPE_KEY, CategoryChartToImgMaker.CATEGORY_CHART_TYPE_LINE);
       //配置图表的标题、XY轴名称
        option.put(ChartToImgMaker.TITLE_KEY, "第一季度温度情况");
        option.put(ChartToImgMaker.X_AXIS_KEY, "月份");
        option.put(ChartToImgMaker.Y_AXIS_KEY, "温度");

        Map lineDate = new HashMap();
        List seriesKeys= new ArrayList();
        seriesKeys.add("1月份");
        seriesKeys.add("2月份");
        seriesKeys.add("3月份");
        //配置X轴的值
        lineDate.put(CategoryChartToImgMaker.SERIES_KEY_LIST, seriesKeys);
        List series1= new ArrayList();
        series1.add(23D);
        series1.add(null);
        series1.add(21D);
        lineDate.put("最高",series1);
        List series2= new ArrayList();
        series2.add(8D);
        series2.add(11D);
        series2.add(5D);
        lineDate.put("最低",series2);
        List series3= new ArrayList();
        series3.add(15.5);
        series3.add(18.0);
        series3.add(13.0);
        lineDate.put("平均",series3);

        String chartImgPath = "D:\\lineImg.jpg";
        CategoryChartToImgMaker categoryChartToImgMaker = new CategoryChartToImgMaker();
        categoryChartToImgMaker.trans(option, lineDate, chartImgPath, 1000, 800);

至此我们已将表格转化为图片,接下来就是将图片插入到excel中

public class POIExcelUtil {
 /**
     * 将图片插入到指定位置,并设定图片所占区域大小,以单元格为单位
     * @param imgPath
     * @param region  图片位置以及大小;
     *                图片左上角所在单元格 => region[0]:col; region[1]: row;
     *                图片大小,单位为一个单元格的宽或高 => region[2]: width; region[3]: height
     * @param patriarch
     * @param workbook
     */
    public static void pictureToPosition(String imgPath, int[] region, HSSFPatriarch patriarch, Workbook workbook) {
        try {
            if (region.length != 4){
                throw new IllegalArgumentException("the region should have 4 items which are col, row, width, height for image");
            }
            ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream();
            BufferedImage bufferImg = ImageIO.read(new File(imgPath));
            ImageIO.write(bufferImg, FilenameUtils.getExtension(imgPath), byteArrayOut);
            //定位图片的位置
            HSSFClientAnchor anchor = new HSSFClientAnchor(0, 0, 0, 0, (short) region[0], region[1], (short) (region[0]+region[2]), region[1]+region[3]);
            patriarch.createPicture(anchor, workbook.addPicture(byteArrayOut.toByteArray(), HSSFWorkbook.PICTURE_TYPE_PNG));
            byteArrayOut.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

写好了工具类,之后就简单了


        POIExcelUtil.pictureToPosition(chartImgPath, new int[]{2,30,10,20}, patriarch, workbook);

POI excel插入图表_第1张图片
左侧的饼图也是示例,只要继承AbstractChartToImgMaker并实现createDataset和createChart即可(地方有限暂时不给代码)。如果你觉得我定义的数据结构很复杂,你也可以自己继承CategoryChartToImgMaker并给出createDataset的实现即可。

你可能感兴趣的:(前端,后台)