需求:生成Excel时支持图表,包括折线图、柱状图、条形图、饼状图、散点图、面积图。
要求:1、开源免费;2、支持跨平台
设计:
1. 使用图片代替Excel中的图表,采用JFreeChart渲染生成图表图片。
2. 采用POI操作Excel。
以下是向Excel2003文件生成折线图的示例(先用JFreeChart生成图片,再用POI将图片加入Excel文件):
效果图:
public static void test() throws Exception {
// 第一步,使用JFreeChart生成折线图的图片
// 图表标题、图表x轴名称、图表y轴名称
String title = "水果时间段销量";
String categoryAxisLabel = "时间";
String valueAxisLabel = "销量";
String[] titles = new String[] { title, categoryAxisLabel, valueAxisLabel };
String[] rowKeys = { "苹果", "橘子", "香蕉" };
String[] colKeys = { "10:00", "11:00", "12:00" };
double[][] data = { { 120, 200, 150 }, { 230, 200, 235 }, { 100, 200, 300 } };
byte[] bytes = LineChartImg.getBytes(titles, 400, 200, rowKeys, colKeys, data);
// 第二步,使用POI将图片放入excel文件
String excel = "F:/Users/zyj/Desktop/2019-09-27 自由报表/生成的报表.xls";
int[] anchors = new int[] { 0, 0, 0, 0, 2, 1, 12, 15 };
XlsHelper.addPicture(excel, 0, anchors, bytes);
}
部分maven:
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>org.jfree</groupId>
<artifactId>jfreechart</artifactId>
<version>1.5.0</version>
</dependency>
使用JFreeChart生成折线图:
import java.awt.Color;
import java.awt.Font;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import org.jfree.chart.ChartColor;
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.NumberTickUnit;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.renderer.category.LineAndShapeRenderer;
import org.jfree.chart.title.LegendTitle;
import org.jfree.chart.title.TextTitle;
import org.jfree.data.category.CategoryDataset;
import org.jfree.data.general.DatasetUtilities;
import org.jfree.ui.RectangleEdge;
/**
* JFreeChart折线图,统一设置折线图的样式
*
* @author zyj
* @date 2019-10-14
*/
public class LineChartImg {
/**
* JFreeChart生成折线图(PNG)
*
* @param titles 折线图的三个标题,0是标题,1是x轴标题,2是y轴标题
* @param width 折线图的宽度
* @param height 折线图的高度
* @param rowKeys y轴数据类别
* @param colKeys x轴的分类
* @param data 数据值
* @return 图片的二进制流
* @throws IOException
*/
public static byte[] getBytes(String[] titles, int width, int height, String[] rowKeys, String[] colKeys, double[][] data) throws IOException {
CategoryDataset dataset = DatasetUtilities.createCategoryDataset(rowKeys, colKeys, data);
// 或者使用下面的方法生成CategoryDataset
// DefaultCategoryDataset dataset = new DefaultCategoryDataset();
// for (int i = 0; i < rowKeys.length; i++) {
// for (int j = 0; j < colKeys.length; j++) {
// dataset.addValue(data[i][j], rowKeys[i], colKeys[j]);
// }
// }
JFreeChart chart = ChartFactory.createLineChart(titles[0], titles[1], titles[2], dataset, PlotOrientation.VERTICAL, true, true, true);
// 图表样式设置
setStyle(chart);
ByteArrayOutputStream os = new ByteArrayOutputStream();
ChartUtilities.writeChartAsPNG(os, chart, width, height);
return os.toByteArray();
}
/**
* JFreeChart折线图样式的统一设置
*
* @param chart JFreeChart
*/
private static void setStyle(JFreeChart chart) {
// 设置图片中的字体和颜色以及字号
Font titleFont = new Font("黑体", Font.BOLD, 12);
Font xfont = new Font("黑体", Font.BOLD, 10);
Font labelFont = new Font("黑体", Font.BOLD, 10);
// 设置图例字体
chart.getLegend().setItemFont(new Font("黑体", Font.BOLD, 10));
// 设置标题字体
chart.setTitle(new TextTitle(chart.getTitle().getText(), titleFont));
// 图形的绘制结构对象
CategoryPlot plot = chart.getCategoryPlot();
// 获取显示线条的对象
LineAndShapeRenderer lasp = (LineAndShapeRenderer) plot.getRenderer();
// 设置拐点是否可见/是否显示拐点
lasp.setBaseShapesVisible(true);
// 设置拐点不同用不同的形状
lasp.setDrawOutlines(true);
// 设置线条是否被显示填充颜色
lasp.setUseFillPaint(false);
// 设置折点的大小
// lasp.setSeriesOutlineStroke(0, new BasicStroke(0.025F));
// lasp.setSeriesOutlineStroke(1, new BasicStroke(0.05F));
// 设置折线大小以及折线的颜色
// LineAndShapeRenderer renderer = (LineAndShapeRenderer) plot.getRenderer();
// renderer.setSeriesStroke(0, new BasicStroke(1.0F));
// renderer.setSeriesPaint(0, new Color(210, 105, 30));
// renderer.setSeriesStroke(1, new BasicStroke(1.0F));
// renderer.setSeriesPaint(1, new Color(0, 191, 255));
// 设置网格线
plot.setDomainGridlinePaint(Color.gray);
plot.setDomainGridlinesVisible(true);
plot.setRangeGridlinePaint(Color.gray);
plot.setRangeGridlinesVisible(true);
// x轴
CategoryAxis domainAxis = plot.getDomainAxis();
// 设置x轴不显示,即让x轴和数据区重合
domainAxis.setAxisLineVisible(false);
// x轴标题
domainAxis.setLabelFont(xfont);
// x轴数据倾斜
domainAxis.setCategoryLabelPositions(CategoryLabelPositions.createUpRotationLabelPositions(0.95D));
// X轴坐标上数值字体
domainAxis.setTickLabelFont(labelFont);
// 设置Y轴间隔
NumberAxis numAxis = (NumberAxis) plot.getRangeAxis();
numAxis.setTickUnit(new NumberTickUnit(50));
// y轴
ValueAxis rangeAxis = plot.getRangeAxis();
rangeAxis.setLabelFont(xfont);
// 设置y轴不显示,即和数据区重合
rangeAxis.setAxisLineVisible(false);
// y轴坐标上数值字体
rangeAxis.setTickLabelFont(labelFont);
rangeAxis.setFixedDimension(0);
CategoryPlot cp = chart.getCategoryPlot();
// 背景色设置
cp.setBackgroundPaint(ChartColor.WHITE);
cp.setRangeGridlinePaint(ChartColor.GRAY);
// 创建图例,设置图例的位置,这里的设置实际不起作用,怎么设都在下边
LegendTitle legendTitle = new LegendTitle(chart.getPlot());
legendTitle.setPosition(RectangleEdge.BOTTOM);
}
}
使用POI将图片添加到Excel2003文件:
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import org.apache.poi.hssf.usermodel.HSSFClientAnchor;
import org.apache.poi.hssf.usermodel.HSSFPatriarch;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.ClientAnchor.AnchorType;
/**
* Excel2003的常用方法
*
* @author zyj
* @date 2019-10-14
*/
public class XlsHelper {
/**
* POI在Excel中添加图片(PNG)
*
* @param excel excel文件的全路径
* @param sheetIndex 图片所在Sheet号(从0开始)
* @param anchors 图片位置,八个参数,前四个表示图片离起始单元格和结束单元格边缘的位置,后四个表示起始和结束单元格的位置;示例
* {0,0,0,0,2,1,12,15}表示从第2列到第12列,从第1行到第15行,单元格内部的边距都是0
* @param bytes 图片的二进制流
* @throws IOException
*/
public static void addPicture(String excel, int sheetIndex, int[] anchors, byte[] bytes) throws IOException {
File file = new File(excel);
// Excel目录是否存在,不存在则创建
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
// Excel文件是否存在,不存在则创建空Excel对象
HSSFWorkbook wb = null;
if (file.exists()) {
FileInputStream fins = new FileInputStream(file);
wb = new HSSFWorkbook(fins);
} else {
wb = new HSSFWorkbook();
wb.createSheet();
sheetIndex = 0;
}
// 添加图片
addPicture(wb, sheetIndex, anchors, bytes);
// 保存excel文件
FileOutputStream fileOut = new FileOutputStream(excel);
wb.write(fileOut);
wb.close();
fileOut.close();
}
/**
* POI在Workbook中添加图片(PNG)
*
* @param wb Excel工作簿
* @param sheetIndex 图片所在Sheet号(从0开始)
* @param anchors 图片位置,八个参数,前四个表示图片离起始单元格和结束单元格边缘的位置,后四个表示起始和结束单元格的位置;示例
* {0,0,0,0,2,1,12,15}表示从第2列到第12列,从第1行到第15行,单元格内部的边距都是0
* @param bytes 图片的二进制流
*/
public static void addPicture(HSSFWorkbook wb, int sheetIndex, int[] anchors, byte[] bytes) {
HSSFSheet sheet = wb.getSheetAt(sheetIndex);
// 画图的顶级管理器,一个sheet只能获取一个(一定要注意这点)
HSSFPatriarch patriarch = sheet.createDrawingPatriarch();
// 设置图片位置
// 八个参数, 前四个表示图片离起始单元格和结束单元格边缘的位置,
// 后四个表示起始和结束单元格的位置, 如下表示从第2列到第12列, 从第1行到第15行,需要注意excel起始位置是0
HSSFClientAnchor anchor = new HSSFClientAnchor(anchors[0], anchors[1], anchors[2], anchors[3], (short) anchors[4], anchors[5], (short) anchors[6], anchors[7]);
anchor.setAnchorType(AnchorType.DONT_MOVE_AND_RESIZE);
// 插入图片
int pictureIndex = wb.addPicture(bytes, HSSFWorkbook.PICTURE_TYPE_PNG);
patriarch.createPicture(anchor, pictureIndex);
}
}