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
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);
//配置图表的标题、X、Y轴名称
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);
左侧的饼图也是示例,只要继承AbstractChartToImgMaker并实现createDataset和createChart即可(地方有限暂时不给代码)。如果你觉得我定义的数据结构很复杂,你也可以自己继承CategoryChartToImgMaker并给出createDataset的实现即可。