-
功能介绍
-
需求由来
整理数据和图表,导入到一份规范化的word中
-
准备工作
需求并不是很明确,就先实现word导出,我采用的是freemarker
首先:搭建一个boot项目
添加pom中的包
<!--web模块用作测试请求-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- freemarker 模版引擎 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<!-- fastjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.44</version>
</dependency>
<!--thymeleaf,方便页面数据传输-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--jquery-->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.4.0</version>
</dependency>
创建word模板
记得一定要用office,不然导出的word可能会出现损坏
接着在word中输入一些信息,如文字,表格等
将文件另存为xml格式,最好是word2003的xml这样,高版本低配低版本的特性,保证下载下来的word都能用
保存后用记事本打开这个xml文件,复制出来,去网上随便找个网站吧xml格式化一下,这样方便我们去阅读
我这里经常用格式化的在线网址:http://www.bejson.com/otherformat/xml/
在项目中创建一个.ftl结尾的文件,将刚刚格式化好的xml复制进去
创建一个Controller方法,方便外部访问,这是测试方法
private final ExportUtil exportUtil;
@Autowired
public WordController(ExportUtil exportUtil) {
this.exportUtil = exportUtil;
}
@RequestMapping(value = "/wordDownload")
public void wordDownload(HttpServletRequest request, HttpServletResponse response) throws IOException {
Map<String, Object> map=new HashMap<>();
map.put("title","hello ! ! 我是一个标题");
List<Map<String, Object>> excelInfo=new ArrayList<>();
for (int i=0;i<3;i++){
Map<String, Object> info=new HashMap<>();
info.put("name","张"+i);
info.put("info","我是第"+i+"个介绍");
excelInfo.add(info);
}
map.put("excelInfo",excelInfo);
exportUtil.exportTemplateWord(response,request,map,"测试","word/Template.ftl");
}
基于freemarker工具类exportUtil
package com.qxl.wordexport.util;
import freemarker.template.Configuration;
import freemarker.template.Template;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.time.LocalDateTime;
import java.util.Map;
/**
* @program: hotline
* @author: Fulin
* @create: 2018-12-10 17:59
**/
@Component
public class ExportUtil {
private static final String ENCODING = "utf-8";
private static final String WORD_CONTENTTYPE = "application/msword";
private static final String EXCEL_CONTENTTYPE = "application/x-download";
// private static final String BASE_PATH = ClassUtils.getDefaultClassLoader().getResourceAsStream()
private static final String TEMPLATEFOLDER = BASE_PATH + File.separator + "templates";
// private static final String TEMPLATEFOLDER = BASE_PATH;
/* private static Configuration configuration;
static {
configuration = new Configuration(Configuration.VERSION_2_3_28);
configuration.setDefaultEncoding("utf-8");
try {
configuration.setDirectoryForTemplateLoading(new File("/word"));
} catch (IOException e) {
e.printStackTrace();
}
}
*/
private final Configuration configuration; //freeMarker configuration
@Autowired
public ExportUtil(Configuration configuration) {
this.configuration = configuration;
}
/**
* freemarker模版导出word
*
* @param response 响应
* @param map 数据
* @param title 标题
* @param ftlFile 文件路径 templates 目录下的
* @throws IOException IO异常
*/
public void exportTemplateWord(HttpServletResponse response, HttpServletRequest request, Map map, String title, String ftlFile) throws IOException {
Template freemarkerTemplate = configuration.getTemplate(ftlFile);
File file;
String fileName = title+LocalDateTime.now().toString() + ".doc";
if (!StringUtils.isEmpty(title)) {
fileName = title + ".doc";
if (isIE(request)) {
fileName = URLEncoder.encode(fileName, "UTF8");
} else {
fileName = new String(fileName.getBytes("UTF-8"), "ISO-8859-1");
}
}
response.setCharacterEncoding("utf-8");
response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
response.setContentType(WORD_CONTENTTYPE);
response.setCharacterEncoding(ENCODING);
file = createDoc(map, freemarkerTemplate);
//java7的新回收特性
tempBuffer(response, file);
}
/**
* freemarker模版导出excel
*
* @param response 响应
* @param map 数据
* @param title 标题
* @param ftlFile 文件路径 templates 目录下的
* @throws IOException IO异常
*/
public void exportTemplateExcel(HttpServletResponse response, Map map, String title, String ftlFile) throws IOException {
Template freemarkerTemplate = configuration.getTemplate(ftlFile);
File file;
String fileName = title + LocalDateTime.now().toString() + ".xls";
response.setHeader("Content-Disposition", "attachment;filename="
.concat(String.valueOf(URLEncoder.encode(fileName, ENCODING))));
response.setContentType(EXCEL_CONTENTTYPE);
response.setCharacterEncoding(ENCODING);
file = createDoc(map, freemarkerTemplate);
tempBuffer(response, file);
}
private static void tempBuffer(HttpServletResponse response, File file) throws IOException {
//java7的新回收特性
try (InputStream fin = new FileInputStream(file); ServletOutputStream out = response.getOutputStream()) {
// 调用工具类的createDoc方法生成Word文档
// 设置浏览器以下载的方式处理该文件名
byte[] buffer = new byte[1024]; // 缓冲区
int bytesToRead = -1;
// 通过循环将读入的Word文件的内容输出到浏览器中
while ((bytesToRead = fin.read(buffer)) != -1) {
out.write(buffer, 0, bytesToRead);
}
} finally {
if (file != null) {
boolean delete = file.delete();
}
}
}
private static File createDoc(Map<?, ?> dataMap, Template template) {
String name = "temp";
File f = new File(name);
try {
//这个地方不能使用FileWriter因为需要指定编码类型否则生成的Word文档会因为有无法识别的编码而无法打开
Writer w = new OutputStreamWriter(new FileOutputStream(f), ENCODING);
template.process(dataMap, w);
w.close();
} catch (Exception ex) {
ex.printStackTrace();
throw new RuntimeException(ex);
}
return f;
}
private static boolean isIE(HttpServletRequest request) {
return request.getHeader("USER-AGENT").toLowerCase().indexOf("msie") > 0 || request.getHeader("USER-AGENT").toLowerCase().indexOf("rv:11.0") > 0 || request.getHeader("USER-AGENT").toLowerCase().indexOf("edge") > 0;
}
}
接着将map中的数据填充在word模板中,打开之前创建的 .ftl 结尾的文件,找到正文
将这个文字替换成freemarker的表达式,取出我们的传进来数据
访问我们项目路径:http://127.0.0.1:8080/wordDownload
文件下载完毕,打开发现我们后台传过去标题也在上面了
导出word功能已经完成,接着进行其他功能的实现
-
开始制作
制作word模板
创建word文档,记得一定要用office
另存为2003xml,后记事本打开,粘贴格式化后,项目中创建 .ftl 结尾文件,把格式化好的xml文件放进 .ftl 文件中
<w:binData>中存放的是图片的base64编码,我们可以把他粘贴出来去在线解析网站中看一下是不是我们之前放的图片
我常用在线解析网站:http://imgbase64.duoshitong.com/
在这需要加上前缀 data:image/png;base64, 后面跟上<w:binData>标签内的字符串,效果如下
这么说,我们只需要将往ftl文件对应的<w:binData>标签中放入图片的base64编码就可以,于是我开始想办法去获取图表的base64编码
1.试用echarts
echarts在平时用的比较多,所以打算先用echarts实现,先去官网找相关的js和文档,通过文档我们找到了这样一个方法,可以直接获取图表的base64编码,但是有一点需要注意,就是图表需要去除动画效果,不然无法获取 去除方法为 animation:false
下面粘贴一下我测试页面代码,我在控制到打印一下我想要的信息
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.springframework.org/schema/mvc">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script th:src="@{/echarts/echarts.min.js}"></script>
<script th:src="@{/webjars/jquery/3.4.0/jquery.js}"></script>
</head>
<body>
<div id="myChart" style="width: 600px;height:600px;"></div>
<script type="text/javascript">
$(function () {
echartsDemo();
});
function echartsDemo() {
var myChart = echarts.init(document.getElementById("myChart"));
option = {
title : {
text: '某站点用户访问来源',
subtext: '纯属虚构',
x:'center'
},
tooltip : {
trigger: 'item',
formatter: "{a} <br/>{b} : {c} ({d}%)"
},
legend: {
orient: 'vertical',
left: 'left',
data: ['直接访问','邮件营销','联盟广告','视频广告','搜索引擎']
},
series : [
{
name: '访问来源',
type: 'pie',
radius : '75%',
center: ['50%', '50%'],
data:[
{value:335, name:'直接访问'},
{value:310, name:'邮件营销'},
{value:234, name:'联盟广告'},
{value:135, name:'视频广告'},
{value:1548, name:'搜索引擎'}
],
itemStyle: {
emphasis: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
],
animation:false
};
myChart.setOption(option);
var imgData = myChart.getConnectedDataURL();
console.log(imgData);
}
</script>
</body>
</html>
拿到生成的base64编码,去看一下对不对
粘贴后,放入之前转码网站,发现没问题,现在只需要把这段base64编码提交到后台,放入ftl模板中,我们的word导出功能就做完了
功能都实现了,那么是不是就结束了呢,不,这只是刚刚开始,由于任务布置的时候只是让实现功能,布置任务的人给我说,项目中的图表是用highcharts
2.试用highcharts
第一次用highcharts,我就去官网看了半天,发现只有一个图片下载的方法,但是这个图片我是拿不到的,我只能换一种思路,用外部的插件,去网上找了半天最后选取了 canvg.js,还有一种是html2canvas.min.js,这里我介绍canvg.js的实现,如果有兴趣可以自己研究一下另外一种,下面是他的前段代码
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script th:src="@{/webjars/jquery/3.4.0/jquery.js}"></script>
<script th:src="@{/highcharts/highcharts.js}"></script>
<script th:src="@{/highcharts/exporting.js}"></script>
<script th:src="@{/canvg/canvg.js}"></script>
</head>
<body>
<h1>首页</h1>
<div id="hightCharts"></div>
<canvas id="canvasId" style="display: none"></canvas>
<script type="text/javascript">
Highcharts.chart('hightCharts', {
chart: {
plotBackgroundColor: null,
plotBorderWidth: null,
plotShadow: false,
type: 'pie'
},
title: {
text: '2018年1月浏览器市场份额'
},
tooltip: {
pointFormat: '{series.name}: <b>{point.percentage:.1f}%</b>'
},
plotOptions: {
pie: {
allowPointSelect: true,
cursor: 'pointer',
dataLabels: {
enabled: true,
format: '<b>{point.name}</b>: {point.percentage:.1f} %',
style: {
color: (Highcharts.theme && Highcharts.theme.contrastTextColor) || 'black'
}
}
}
},
series: [{
name: 'Brands',
colorByPoint: true,
data: [{
name: 'Chrome',
y: 61.41,
sliced: true,
selected: true
}, {
name: 'Internet Explorer',
y: 11.84
}, {
name: 'Firefox',
y: 10.85
}, {
name: 'Edge',
y: 4.67
}, {
name: 'Safari',
y: 4.18
}, {
name: 'Sogou Explorer',
y: 1.64
}, {
name: 'Opera',
y: 1.6
}, {
name: 'QQ',
y: 1.2
}, {
name: 'Other',
y: 2.61
}]
}]
});
var chartData = $("#hightCharts").highcharts().getSVG();
canvg(canvasId,chartData);
function convertCanvas(canvas) {
var image = new Image();
image.src = canvas.toDataURL("image/png");
return image
}
var canvas = document.createElement('canvasId');
var imageData = convertCanvas(canvasId).src;
console.log(imageData);
</script>
</body>
</html>
在控制台上拿到打印的信息,放到在线解码网站,图片生成成功,另外我这找到另外一种后台的生成方法,挺好用的,但是如果生成多张图片,可能会比较麻烦,因为这篇文章中把前台单独生成了一个项目,以请求的方式拿到图片,这是链接
弄了这么多感觉已经到了尾声,终于,我拿到了项目!发现项目里压根没有highcharts,但是我找到了amchart(此处应有黑人感叹号!!!),这是什么鬼,我一搞后台的,用了这么多前台的插件,但是还没完成?!算了,继续看amchart吧
3.试用amchart
心平气和去amchart官网翻文档,中间踩的坑就不多说了,下面是官方文档给的方法
接着是我的前段代码
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script th:src="@{/webjars/jquery/3.4.0/jquery.js}"></script>
<!-- Resources -->
<script src="https://www.amcharts.com/lib/4/core.js"></script>
<script th:src="@{/amcharts/charts.js}"></script>
</head>
<body>
<h1>首页</h1>
<!-- HTML -->
<div id="chartdiv" style="width: 100%;height: 500px"></div>
<script type="text/javascript">
$(function () {
aaa();
});
function aaa() {
var chart = am4core.create("chartdiv", am4charts.PieChart);
chart.data = [ {
"country": "Lithuania",
"litres": 501.9
}, {
"country": "Czech Republic",
"litres": 301.9
}, {
"country": "Ireland",
"litres": 201.1
}, {
"country": "Germany",
"litres": 165.8
}, {
"country": "Australia",
"litres": 139.9
}, {
"country": "Austria",
"litres": 128.3
}, {
"country": "UK",
"litres": 99
}, {
"country": "Belgium",
"litres": 60
}, {
"country": "The Netherlands",
"litres": 50
} ];
var pieSeries = chart.series.push(new am4charts.PieSeries());
pieSeries.dataFields.value = "litres";
pieSeries.dataFields.category = "country";
chart.exporting.getImage("png").then(function(imgData) {
console.log(imgData);
});
}
</script>
</body>
</html>
控制台拿到打印信息,没问题
4.坑爹的IE7
这回总可以了吧,欣喜的跑起项目,看着项目再跑,总觉得事情没有这么简单,果然Chrome不兼容,赶紧过去问了一下,适用于IE,并且还要是兼容模式,赶紧打开IE调试兼容模式,看到默认是IE7,心里已经凉了半截,因为这几个图表只能适用于IE9+,我试试看调高一点的版本,结果凉凉,突然想到项目里用的是amchart,那么我amchart一定是能用的,兴奋赶紧去尝试一下,发现项目中amchart是老版本,新版本的方法不兼容,凉透了,脑中灵光一现,不是还有一个html2canvas.js嘛,麻溜的就去官网看,结果发现,最低适配到IE9,这可咋整,难道天要亡我?不行不行,我又去任务发布人哪里问了一下,能不能不用amchart,因为我实现不了,他说可以呀,只要基础的饼图,柱状图,折线图有就行,别太丑美观一点的。之前朋友说有java纯后台生成的图表,我网上了解了一下,有个jfreechart的,感觉可以试试.....
5.jfreechart
已经到这个时候了,没有退路了,硬着头皮试试呗,看了几篇博客,总结了一下,发现挺简单的,用的pom包
<!--jfreechart--> <dependency> <groupId>jfree</groupId> <artifactId>jfreechart</artifactId> <version>1.0.13</version> </dependency>
package com.qxl.wordexport.util;
import com.qxl.wordexport.entity.Serie;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.StandardChartTheme;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.axis.DateTickUnit;
import org.jfree.chart.axis.DateTickUnitType;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.block.BlockBorder;
import org.jfree.chart.labels.*;
import org.jfree.chart.plot.*;
import org.jfree.chart.renderer.category.BarRenderer;
import org.jfree.chart.renderer.category.LineAndShapeRenderer;
import org.jfree.chart.renderer.category.StackedBarRenderer;
import org.jfree.chart.renderer.category.StandardBarPainter;
import org.jfree.chart.renderer.xy.StandardXYBarPainter;
import org.jfree.chart.renderer.xy.XYBarRenderer;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.category.DefaultCategoryDataset;
import org.jfree.data.general.DefaultPieDataset;
import org.jfree.data.time.Day;
import org.jfree.data.time.TimeSeries;
import org.jfree.ui.RectangleInsets;
import org.jfree.ui.TextAnchor;
import java.awt.*;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Vector;
/**
* Jfreechart工具类
* <p>
* 解决中午乱码问题<br>
* 用来创建类别图表数据集、创建饼图数据集、时间序列图数据集<br>
* 用来对柱状图、折线图、饼图、堆积柱状图、时间序列图的样式进行渲染<br>
* 设置X-Y坐标轴样式
* <p>
* 引自文章:https://www.liangzl.com/get-article-detail-26733.html
*
*/
public class JfreeChartStyle {
private static String NO_DATA_MSG = "数据加载失败";
private static Font FONT = new Font("宋体", Font.PLAIN, 12);
public static Color[] CHART_COLORS = {
new Color(31,129,188), new Color(92,92,97), new Color(144,237,125), new Color(255,188,117),
new Color(153,158,255), new Color(255,117,153), new Color(253,236,109), new Color(128,133,232),
new Color(158,90,102),new Color(255, 204, 102) };// 颜色
static {
setChartTheme();
}
public JfreeChartStyle() {
}
/**
* 中文主题样式 解决乱码
*/
public static void setChartTheme() {
// 设置中文主题样式 解决乱码
StandardChartTheme chartTheme = new StandardChartTheme("CN");
// 设置标题字体
chartTheme.setExtraLargeFont(FONT);
// 设置图例的字体
chartTheme.setRegularFont(FONT);
// 设置轴向的字体
chartTheme.setLargeFont(FONT);
chartTheme.setSmallFont(FONT);
chartTheme.setTitlePaint(new Color(51, 51, 51));
chartTheme.setSubtitlePaint(new Color(85, 85, 85));
chartTheme.setLegendBackgroundPaint(Color.WHITE);// 设置标注
chartTheme.setLegendItemPaint(Color.BLACK);//
chartTheme.setChartBackgroundPaint(Color.WHITE);
// 绘制颜色绘制颜色.轮廓供应商
// paintSequence,outlinePaintSequence,strokeSequence,outlineStrokeSequence,shapeSequence
Paint[] OUTLINE_PAINT_SEQUENCE = new Paint[] { Color.WHITE };
// 绘制器颜色源
DefaultDrawingSupplier drawingSupplier = new DefaultDrawingSupplier(CHART_COLORS, CHART_COLORS, OUTLINE_PAINT_SEQUENCE,
DefaultDrawingSupplier.DEFAULT_STROKE_SEQUENCE, DefaultDrawingSupplier.DEFAULT_OUTLINE_STROKE_SEQUENCE,
DefaultDrawingSupplier.DEFAULT_SHAPE_SEQUENCE);
chartTheme.setDrawingSupplier(drawingSupplier);
chartTheme.setPlotBackgroundPaint(Color.WHITE);// 绘制区域
chartTheme.setPlotOutlinePaint(Color.WHITE);// 绘制区域外边框
chartTheme.setLabelLinkPaint(new Color(8, 55, 114));// 链接标签颜色
chartTheme.setLabelLinkStyle(PieLabelLinkStyle.CUBIC_CURVE);
chartTheme.setAxisOffset(new RectangleInsets(5, 12, 5, 12));
chartTheme.setDomainGridlinePaint(new Color(192, 208, 224));// X坐标轴垂直网格颜色
chartTheme.setRangeGridlinePaint(new Color(192, 192, 192));// Y坐标轴水平网格颜色
chartTheme.setBaselinePaint(Color.WHITE);
chartTheme.setCrosshairPaint(Color.BLUE);// 不确定含义
chartTheme.setAxisLabelPaint(new Color(51, 51, 51));// 坐标轴标题文字颜色
chartTheme.setTickLabelPaint(new Color(67, 67, 72));// 刻度数字
chartTheme.setBarPainter(new StandardBarPainter());// 设置柱状图渲染
chartTheme.setXYBarPainter(new StandardXYBarPainter());// XYBar 渲染
chartTheme.setItemLabelPaint(Color.black);
chartTheme.setThermometerPaint(Color.white);// 温度计
ChartFactory.setChartTheme(chartTheme);
}
/**
* 必须设置文本抗锯齿
*/
public static void setAntiAlias(JFreeChart chart) {
chart.setTextAntiAlias(false);
}
/**
* 设置图例无边框,默认黑色边框
*/
public static void setLegendEmptyBorder(JFreeChart chart) {
chart.getLegend().setFrame(new BlockBorder(Color.WHITE));
}
/**
* 创建类别数据集合
*/
public static DefaultCategoryDataset createDefaultCategoryDataset(Vector<Serie> series, String[] categories) {
DefaultCategoryDataset dataset = new DefaultCategoryDataset();
for (Serie serie : series) {
String name = serie.getName();
Vector<Object> data = serie.getData();
if (data != null && categories != null && data.size() == categories.length) {
for (int index = 0; index < data.size(); index++) {
String value = data.get(index) == null ? "" : data.get(index).toString();
if (isPercent(value)) {
value = value.substring(0, value.length() - 1);
}
if (isNumber(value)) {
dataset.setValue(Double.parseDouble(value), name, categories[index]);
}
}
}
}
return dataset;
}
/**
* 创建饼图数据集合
*/
public static DefaultPieDataset createDefaultPieDataset(String[] categories, Object[] datas) {
DefaultPieDataset dataset = new DefaultPieDataset();
for (int i = 0; i < categories.length && categories != null; i++) {
String value = datas[i].toString();
if (isPercent(value)) {
value = value.substring(0, value.length() - 1);
}
if (isNumber(value)) {
dataset.setValue(categories[i], Double.valueOf(value));
}
}
return dataset;
}
/**
* 创建时间序列数据
*
* @param category
* 类别
* @param dateValues
* 日期-值 数组
* @param xAxisTitle
* X坐标轴标题
* @return
*/
public static TimeSeries createTimeseries(String category, Vector<Object[]> dateValues) {
TimeSeries timeseries = new TimeSeries(category);
if (dateValues != null) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
for (Object[] objects : dateValues) {
Date date = null;
try {
date = dateFormat.parse(objects[0].toString());
} catch (ParseException e) {
}
String sValue = objects[1].toString();
double dValue = 0;
if (date != null && isNumber(sValue)) {
dValue = Double.parseDouble(sValue);
timeseries.add(new Day(date), dValue);
}
}
}
return timeseries;
}
/**
* 设置 折线图样式
*
* @param plot
* @param isShowDataLabels
* 是否显示数据标签 默认不显示节点形状
*/
public static void setLineRender(CategoryPlot plot, boolean isShowDataLabels) {
setLineRender(plot, isShowDataLabels, false);
}
/**
* 设置折线图样式
*
* @param plot
* @param isShowDataLabels
* 是否显示数据标签
*/
public static void setLineRender(CategoryPlot plot, boolean isShowDataLabels, boolean isShapesVisible) {
plot.setNoDataMessage(NO_DATA_MSG);
plot.setInsets(new RectangleInsets(10, 10, 0, 10), false);
LineAndShapeRenderer renderer = (LineAndShapeRenderer) plot.getRenderer();
renderer.setStroke(new BasicStroke(1.5F));
if (isShowDataLabels) {
renderer.setBaseItemLabelsVisible(true);
renderer.setBaseItemLabelGenerator(new StandardCategoryItemLabelGenerator(StandardCategoryItemLabelGenerator.DEFAULT_LABEL_FORMAT_STRING,
NumberFormat.getInstance()));
renderer.setBasePositiveItemLabelPosition(new ItemLabelPosition(ItemLabelAnchor.OUTSIDE1, TextAnchor.BOTTOM_CENTER));// weizhi
}
renderer.setBaseShapesVisible(isShapesVisible);// 数据点绘制形状
setXAixs(plot);
setYAixs(plot);
}
/**
* 设置时间序列图样式
*
* @param plot
* @param isShowData
* 是否显示数据
* @param isShapesVisible
* 是否显示数据节点形状
*/
public static void setTimeSeriesRender(Plot plot, boolean isShowData, boolean isShapesVisible) {
XYPlot xyplot = (XYPlot) plot;
xyplot.setNoDataMessage(NO_DATA_MSG);
xyplot.setInsets(new RectangleInsets(10, 10, 5, 10));
XYLineAndShapeRenderer xyRenderer = (XYLineAndShapeRenderer) xyplot.getRenderer();
xyRenderer.setBaseItemLabelGenerator(new StandardXYItemLabelGenerator());
xyRenderer.setBaseShapesVisible(false);
if (isShowData) {
xyRenderer.setBaseItemLabelsVisible(true);
xyRenderer.setBaseItemLabelGenerator(new StandardXYItemLabelGenerator());
xyRenderer.setBasePositiveItemLabelPosition(new ItemLabelPosition(ItemLabelAnchor.OUTSIDE1, TextAnchor.BOTTOM_CENTER));// weizhi
}
xyRenderer.setBaseShapesVisible(isShapesVisible);// 数据点绘制形状
DateAxis domainAxis = (DateAxis) xyplot.getDomainAxis();
domainAxis.setAutoTickUnitSelection(false);
DateTickUnit dateTickUnit = new DateTickUnit(DateTickUnitType.YEAR, 1, new SimpleDateFormat("yyyy-MM")); // 第二个参数是时间轴间距
domainAxis.setTickUnit(dateTickUnit);
StandardXYToolTipGenerator xyTooltipGenerator = new StandardXYToolTipGenerator("{1}:{2}", new SimpleDateFormat("yyyy-MM-dd"), new DecimalFormat("0"));
xyRenderer.setBaseToolTipGenerator(xyTooltipGenerator);
setXY_XAixs(xyplot);
setXY_YAixs(xyplot);
}
/**
* 设置时间序列图样式 -默认不显示数据节点形状
*
* @param plot
* @param isShowData
* 是否显示数据
*/
public static void setTimeSeriesRender(Plot plot, boolean isShowData) {
setTimeSeriesRender(plot, isShowData, false);
}
/**
* 设置时间序列图渲染:但是存在一个问题:如果timeseries里面的日期是按照天组织, 那么柱子的宽度会非常小,和直线一样粗细
*
* @param plot
* @param isShowDataLabels
*/
public static void setTimeSeriesBarRender(Plot plot, boolean isShowDataLabels) {
XYPlot xyplot = (XYPlot) plot;
xyplot.setNoDataMessage(NO_DATA_MSG);
XYBarRenderer xyRenderer = new XYBarRenderer(0.1D);
xyRenderer.setBaseItemLabelGenerator(new StandardXYItemLabelGenerator());
if (isShowDataLabels) {
xyRenderer.setBaseItemLabelsVisible(true);
xyRenderer.setBaseItemLabelGenerator(new StandardXYItemLabelGenerator());
}
StandardXYToolTipGenerator xyTooltipGenerator = new StandardXYToolTipGenerator("{1}:{2}", new SimpleDateFormat("yyyy-MM-dd"), new DecimalFormat("0"));
xyRenderer.setBaseToolTipGenerator(xyTooltipGenerator);
setXY_XAixs(xyplot);
setXY_YAixs(xyplot);
}
/**
* 设置柱状图渲染
*
* @param plot
* @param isShowDataLabels
*/
public static void setBarRenderer(CategoryPlot plot, boolean isShowDataLabels) {
plot.setNoDataMessage(NO_DATA_MSG);
plot.setInsets(new RectangleInsets(10, 10, 5, 10));
BarRenderer renderer = (BarRenderer) plot.getRenderer();
renderer.setBaseItemLabelGenerator(new StandardCategoryItemLabelGenerator());
renderer.setMaximumBarWidth(0.075);// 设置柱子最大宽度
if (isShowDataLabels) {
renderer.setBaseItemLabelsVisible(true);
}
setXAixs(plot);
setYAixs(plot);
}
/**
* 设置堆积柱状图渲染
*
* @param plot
*/
public static void setStackBarRender(CategoryPlot plot) {
plot.setNoDataMessage(NO_DATA_MSG);
plot.setInsets(new RectangleInsets(10, 10, 5, 10));
StackedBarRenderer renderer = (StackedBarRenderer) plot.getRenderer();
renderer.setBaseItemLabelGenerator(new StandardCategoryItemLabelGenerator());
plot.setRenderer(renderer);
setXAixs(plot);
setYAixs(plot);
}
/**
* 设置类别图表(CategoryPlot) X坐标轴线条颜色和样式
*
* @param axis
*/
public static void setXAixs(CategoryPlot plot) {
Color lineColor = new Color(31, 121, 170);
plot.getDomainAxis().setAxisLinePaint(lineColor);// X坐标轴颜色
plot.getDomainAxis().setTickMarkPaint(lineColor);// X坐标轴标记|竖线颜色
}
/**
* 设置类别图表(CategoryPlot) Y坐标轴线条颜色和样式 同时防止数据无法显示
*
* @param axis
*/
public static void setYAixs(CategoryPlot plot) {
Color lineColor = new Color(192, 208, 224);
ValueAxis axis = plot.getRangeAxis();
axis.setAxisLinePaint(lineColor);// Y坐标轴颜色
axis.setTickMarkPaint(lineColor);// Y坐标轴标记|竖线颜色
// 隐藏Y刻度
axis.setAxisLineVisible(false);
axis.setTickMarksVisible(false);
// Y轴网格线条
plot.setRangeGridlinePaint(new Color(192, 192, 192));
plot.setRangeGridlineStroke(new BasicStroke(1));
plot.getRangeAxis().setUpperMargin(0.1);// 设置顶部Y坐标轴间距,防止数据无法显示
plot.getRangeAxis().setLowerMargin(0.1);// 设置底部Y坐标轴间距
}
/**
* 设置XY图表(XYPlot) X坐标轴线条颜色和样式
*
* @param axis
*/
public static void setXY_XAixs(XYPlot plot) {
Color lineColor = new Color(31, 121, 170);
plot.getDomainAxis().setAxisLinePaint(lineColor);// X坐标轴颜色
plot.getDomainAxis().setTickMarkPaint(lineColor);// X坐标轴标记|竖线颜色
}
/**
* 设置XY图表(XYPlot) Y坐标轴线条颜色和样式 同时防止数据无法显示
*
* @param axis
*/
public static void setXY_YAixs(XYPlot plot) {
Color lineColor = new Color(192, 208, 224);
ValueAxis axis = plot.getRangeAxis();
axis.setAxisLinePaint(lineColor);// X坐标轴颜色
axis.setTickMarkPaint(lineColor);// X坐标轴标记|竖线颜色
// 隐藏Y刻度
axis.setAxisLineVisible(false);
axis.setTickMarksVisible(false);
// Y轴网格线条
plot.setRangeGridlinePaint(new Color(192, 192, 192));
plot.setRangeGridlineStroke(new BasicStroke(1));
plot.setDomainGridlinesVisible(false);
plot.getRangeAxis().setUpperMargin(0.12);// 设置顶部Y坐标轴间距,防止数据无法显示
plot.getRangeAxis().setLowerMargin(0.12);// 设置底部Y坐标轴间距
}
/**
* 设置饼状图渲染
*/
public static void setPieRender(Plot plot) {
plot.setNoDataMessage(NO_DATA_MSG);
plot.setInsets(new RectangleInsets(10, 10, 5, 10));
PiePlot piePlot = (PiePlot) plot;
piePlot.setInsets(new RectangleInsets(0, 0, 0, 0));
piePlot.setCircular(true);// 圆形
// piePlot.setSimpleLabels(true);// 简单标签
piePlot.setLabelGap(0.01);
piePlot.setInteriorGap(0.05D);
piePlot.setLegendItemShape(new Rectangle(10, 10));// 图例形状
piePlot.setIgnoreNullValues(true);
piePlot.setLabelBackgroundPaint(null);// 去掉背景色
piePlot.setLabelShadowPaint(null);// 去掉阴影
piePlot.setLabelOutlinePaint(null);// 去掉边框
piePlot.setShadowPaint(null);
// 0:category 1:value:2 :percentage
piePlot.setLabelGenerator(new StandardPieSectionLabelGenerator("{0}:{2}"));// 显示标签数据
}
/**
* 是不是一个%形式的百分比
*
* @param str
* @return
*/
public static boolean isPercent(String str) {
return str != null ? str.endsWith("%") && isNumber(str.substring(0, str.length() - 1)) : false;
}
/**
* 是不是一个数字
*
* @param str
* @return
*/
public static boolean isNumber(String str) {
return str != null ? str.matches("^[-+]?(([0-9]+)((([.]{0})([0-9]*))|(([.]{1})([0-9]+))))$") : false;
}
}
自己简单封装了一下饼图,柱状图和折线图的调用方法
package com.qxl.wordexport.util;
import com.qxl.wordexport.entity.Serie;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.category.CategoryDataset;
import org.jfree.data.general.DefaultPieDataset;
import sun.misc.BASE64Encoder;
import java.awt.*;
import java.io.*;
import java.util.Date;
import java.util.Locale;
import java.util.Vector;
/**
* jfreechart生成工具类
* 生成效果:在指定目录下生成png图片,生成图片base64码
* 可生成类型:折线图,柱状图,饼图
*/
public class JfreeChartUtil {
/**
* 图片存储位置可以写在配置文件中
*/
private static String CHART_IMG_FILE="E:/boot/charts/";
/**
* 把jfreechart生成的图片转化为base64字符串
* @param type 判断是否统计图类型 (折线图,柱状图,饼图)
* @param series 柱状(折线)图数据
* @param categories 类别
* @param data 饼图数据
* @param tile 图表标题
* @param categoryAxisLabel 柱状(折线)图底边信息
* @param valueAxisLabel 柱状(折线)图左侧边信息
* @return 返回base64图片编码
*/
public static String getImageStr(String type, Vector<Serie> series, String[] categories, Object[] data, String tile, String categoryAxisLabel, String valueAxisLabel) throws IOException {
JFreeChart jfreechart = null;
if("折线图".equals(type)){
jfreechart = createChart(series,categories,tile,categoryAxisLabel,valueAxisLabel);
}
if("柱状图".equals(type)){
jfreechart = createBarChart(series,categories,tile,categoryAxisLabel,valueAxisLabel);
}
if("饼图".equals(type)){
jfreechart =createPieChart(data,categories,tile);
}
BASE64Encoder BASE64 = new BASE64Encoder();
ByteArrayOutputStream bas = new ByteArrayOutputStream();
try {
assert jfreechart != null;
ChartUtilities.writeChartAsJPEG(bas, 1.0f, jfreechart, 400, 300, null);
bas.flush();
bas.close();
} catch (IOException e) {
e.printStackTrace();
}
byte[] byteArray = bas.toByteArray();
try {
InputStream is = new ByteArrayInputStream(byteArray);
byteArray = new byte[is.available()];
is.read(byteArray);
is.close();
} catch (IOException e) {
e.printStackTrace();
}
return BASE64.encode(byteArray);
}
/**
* jfreechart生成图片并存放在指定位置
* @param type 判断是否统计图类型 (折线图,柱状图,饼图)
* @param series 柱状(折线)图数据
* @param categories 类别
* @param data 饼图数据
* @param tile 图表标题
* @param categoryAxisLabel 柱状(折线)图底边信息
* @param valueAxisLabel 柱状(折线)图左侧边信息
*/
public static void createPng(String type, Vector<Serie> series, String[] categories, Object[] data, String tile, String categoryAxisLabel, String valueAxisLabel) {
JFreeChart jfreechart = null;
if("折线图".equals(type)){
jfreechart = createChart(series,categories,tile,categoryAxisLabel,valueAxisLabel);
}
if("柱状图".equals(type)){
jfreechart = createBarChart(series,categories,tile,categoryAxisLabel,valueAxisLabel);
}
if("饼图".equals(type)){
jfreechart =createPieChart(data,categories,tile);
}
OutputStream os;
String imgURl=CHART_IMG_FILE + (new Date()).getTime()+".png";
try {
os = new FileOutputStream(imgURl);
assert jfreechart != null;
ChartUtilities.writeChartAsPNG(os, jfreechart, 800, 400);
os.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 折线图
*/
private static JFreeChart createChart(Vector<Serie> series, String[] categories,String tile,String categoryAxisLabel,String valueAxisLabel) {
CategoryDataset dataset = JfreeChartStyle.createDefaultCategoryDataset(series,categories);
//标题,x轴,y轴,数据集,图标方向,是否显示图例,是否生成工具,是否生成url
JFreeChart line = ChartFactory.createLineChart("折线图", "时间", "销售额(百万)", dataset, PlotOrientation.VERTICAL, true,
true, false);
line.setBackgroundPaint(new Color(255, 255, 255));
//设置标题文字
line.getTitle().setFont(new Font("黑体", Font.BOLD, 18));
//设置底部图例字体
line.getLegend().setItemFont(new Font("黑体", Font.BOLD, 12));
CategoryPlot plot = line.getCategoryPlot();
JfreeChartStyle.setLineRender(plot,true);
JfreeChartStyle.setChartTheme();
JfreeChartStyle.setAntiAlias(line);
JfreeChartStyle.setLegendEmptyBorder(line);
return line;
}
/**
* 柱状图生成
* @return 返回图表
*/
private static JFreeChart createBarChart(Vector<Serie> series, String[] categories,String tile,String categoryAxisLabel,String valueAxisLabel){
CategoryDataset dataset = JfreeChartStyle.createDefaultCategoryDataset(series,categories);
//标题,x轴,y轴,数据集,图标方向,是否显示图例,是否生成工具,是否生成url
JFreeChart barChart = ChartFactory.createBarChart(tile, categoryAxisLabel, valueAxisLabel, dataset, PlotOrientation.VERTICAL, true, true, false);
barChart.setBackgroundPaint(new Color(255, 255, 255));
//设置标题文字
barChart.getTitle().setFont(new Font("黑体", Font.BOLD, 18));
//设置底部图例字体
barChart.getLegend().setItemFont(new Font("黑体", Font.BOLD, 12));
CategoryPlot plot = barChart.getCategoryPlot();
JfreeChartStyle.setBarRenderer(plot,true);
JfreeChartStyle.setChartTheme();
JfreeChartStyle.setAntiAlias(barChart);
JfreeChartStyle.setLegendEmptyBorder(barChart);
return barChart;
}
/**
* 饼图
*/
private static JFreeChart createPieChart(Object[] objects, String[] categories,String title){
DefaultPieDataset dataset = JfreeChartStyle.createDefaultPieDataset(categories,objects);
JFreeChart jFreeChart=ChartFactory.createPieChart(title, dataset,true, true,Locale.SIMPLIFIED_CHINESE);
jFreeChart.setBackgroundPaint(new Color(255, 255, 255));
//设置标题文字
jFreeChart.getTitle().setFont(new Font("黑体", Font.BOLD, 18));
//设置底部图例字体
jFreeChart.getLegend().setItemFont(new Font("黑体", Font.BOLD, 12));
JfreeChartStyle.setPieRender(jFreeChart.getPlot());
return jFreeChart;
}
}
数据实体类
package com.qxl.wordexport.entity;
import java.io.Serializable;
import java.util.Vector;
/**
* 系列:名字和数据集合 构成一条曲线</br> 可以将serie看作一根线或者一根柱子:
*
* <p>
* 参照JS图表来描述数据:</br> series: [{ name: 'Tokyo', data: [7.0, 6.9, 9.5, 14.5]
* },</br> { name: 'New York', data: [-0.2, 0.8, 5.7, 11.3} ]</br>
* </p>
*
*/
public class Serie implements Serializable {
private static final long serialVersionUID = 1L;
private String name;// 名字
private Vector<Object> data;// 数据值
public Serie() {
}
/**
*
* @param name
* 名称(线条名称)
* @param data
* 数据(线条上的所有数据值)
*/
public Serie(String name, Vector<Object> data) {
this.name = name;
this.data = data;
}
/**
*
* @param name
* 名称(线条名称)
* @param array
* 数据(线条上的所有数据值)
*/
public Serie(String name, Object[] array) {
this.name = name;
if (array != null) {
data = new Vector<Object>(array.length);
for (int i = 0; i < array.length; i++) {
data.add(array[i]);
}
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Vector<Object> getData() {
return data;
}
public void setData(Vector<Object> data) {
this.data = data;
}
}
下面是我的测试方法:
@Test
public void test() throws IOException {
Vector<Serie> list=new Vector<Serie>();
//数据1
Serie serie=new Serie();
Vector<Object> vector=new Vector<>();
serie.setName("苹果");
vector.add("2");
vector.add("8");
vector.add("4");
vector.add("6");
vector.add("8");
serie.setData(vector);
list.add(serie);
//数据2
Serie serie1=new Serie();
Vector<Object> vector1=new Vector<>();
serie1.setName("香蕉");
vector1.add("5");
vector1.add("7");
vector1.add("4");
vector1.add("9");
vector1.add("3");
serie1.setData(vector1);
list.add(serie1);
//数据3
Serie serie2=new Serie();
Vector<Object> vector2=new Vector<>();
serie2.setName("西瓜");
vector2.add("1");
vector2.add("3");
vector2.add("9");
vector2.add("8.5");
vector2.add("7");
serie2.setData(vector2);
list.add(serie2);
//类别
String[] categories={"一月份","二月份","三月份","四月份","五月份"};
//饼图数据
Object[] objects={"10","20","50","10","20"};
//生成本地图片
JfreeChartEntity.createPng("柱状图", list, categories, null,"这是个柱状图","这是底边类别","这是侧边信息");
JfreeChartEntity.createPng("折线图", list, categories, null,"这是个折线图","这是底边类别","这是侧边信息");
JfreeChartEntity.createPng("饼图", null, categories, objects,"这是个饼图","这是底边类别","这是侧边信息");
//生成base64编码
String imgInfo=JfreeChartEntity.getImageStr("柱状图",list,categories,null,"这是个柱状图","这是底边类别","这是侧边信息");
String imgInfo1=JfreeChartEntity.getImageStr("折线图",list,categories,null,"这是个折线图","这是底边类别","这是侧边信息");
String imgInfo2=JfreeChartEntity.getImageStr("饼图", null, categories, objects,"这是个饼图","这是底边类别","这是侧边信息");
System.out.println(imgInfo);
System.out.println("===============================================");
System.out.println(imgInfo1);
System.out.println("===============================================");
System.out.println(imgInfo2);
}
功能完成,只需要嵌入实际业务中即可
-
总结
这个功能难度不高,大多是因为自己粗心造成,但是功能圆满完成还是挺开心的,同时也给自己敲响了警钟,在不清楚实际开发和使用环境之前,不要贸然下手去做,不然会浪费大量的时间成本,不过我也学到了这么多前端的图表插件,也算是很好的收获了
这是本人的第一篇博客,如有错误请及时帮我纠正,欢迎一起交流技术,QQ群:826953936,QQ3293403426,微信QXL777Flower