本地生成echarts图片并保存到pdf
一、所需工具
1、phantomjs
官网下载:http://phantomjs.org/download.html
国内镜像:http://npm.taobao.org/dist/phantomjs/
2、EChartsConvert
https://gitee.com/hbun_gao/echartsconvert-master
二、Maven依赖
<!--模板引擎-->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.28</version>
</dependency>
<!--用于发送http请求(调用本地echarts服务)-->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.7</version>
</dependency>
<!--用于处理json数据-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.56</version>
</dependency>
<!--itext依赖-->
<dependency>
<groupId>com.itextpdf.tool</groupId>
<artifactId>xmlworker</artifactId>
<version>5.5.9</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>
<dependency>
<groupId>org.xhtmlrenderer</groupId>
<artifactId>flying-saucer-pdf-itext5</artifactId>
<version>9.0.3</version>
</dependency>
三、运行EchartsConvert
./phantomjs-2.1.1-macosx/bin/phantomjs ./echartsconvert-master/echarts-convert.js -s -p 6666
echart服务拉下来就能用,同样的 phantomjs 解压就可以使用,如果长期需要使用的话,建议配置环境变量
phantomjs 配置到 bin 目录 echartsconvert-master配置到echartsconvert-master就可以了
我这里把这两个文件放到了同一个目录下,所以就可以直接就这样启动了,-p 映射下端口(尽量是不常用的)免得以后端口占用的时候忘记了
四、工具类
1.用于发送http请求
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
public class HttpUtil {
public static String post(String url, Map<String, String> params, String charset)
throws ClientProtocolException, IOException {
String responseEntity = "";
// 创建CloseableHttpClient对象
CloseableHttpClient client = HttpClients.createDefault();
// 创建post方式请求对象
HttpPost httpPost = new HttpPost(url);
// 生成请求参数
List<NameValuePair> nameValuePairs = new ArrayList<>();
if (params != null) {
for (Entry<String, String> entry : params.entrySet()) {
nameValuePairs.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
}
}
// 将参数添加到post请求中
httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs, charset));
// 发送请求,获取结果(同步阻塞)
CloseableHttpResponse response = client.execute(httpPost);
// 获取响应实体
HttpEntity entity = response.getEntity();
if (entity != null) {
// 按指定编码转换结果实体为String类型
responseEntity = EntityUtils.toString(entity, charset);
}
// 释放资源
EntityUtils.consume(entity);
response.close();
return responseEntity;
}
}
2.Freemarker模板工具
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Map;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
public class FreemarkerUtil {
private static final String path = FreemarkerUtil.class.getClassLoader().getResource("").getPath();
public static String generateString(String templateFileName, String templateDirectory, Map<String, Object> datas)
throws IOException, TemplateException {
Configuration configuration = new Configuration(Configuration.VERSION_2_3_0);
// 设置默认编码
configuration.setDefaultEncoding("UTF-8");
// 设置模板所在文件夹
configuration.setDirectoryForTemplateLoading(new File(path + templateDirectory));
// 生成模板对象
Template template = configuration.getTemplate(templateFileName);
// 将datas写入模板并返回
try (StringWriter stringWriter = new StringWriter()) {
template.process(datas, stringWriter);
stringWriter.flush();
return stringWriter.getBuffer().toString();
}
}
}
3.生成Echarts工具类
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.apache.http.client.ClientProtocolException;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
public class EchartsUtil {
//这里是你的服务地址
private static String url = "http://localhost:6666";
private static final String SUCCESS_CODE = "1";
public static String generateEchartsBase64(String option) throws ClientProtocolException, IOException {
String base64 = "";
if (option == null) {
return base64;
}
option = option.replaceAll("\\s+", "").replaceAll("\"", "'");
// 将option字符串作为参数发送给echartsConvert服务器
Map<String, String> params = new HashMap<>();
params.put("opt", option);
String response = HttpUtil.post(url, params, "utf-8");
// 解析echartsConvert响应
JSONObject responseJson = JSON.parseObject(response);
String code = responseJson.getString("code");
// 如果echartsConvert正常返回
if (SUCCESS_CODE.equals(code)) {
base64 = responseJson.getString("data");
}
// 未正常返回
else {
String string = responseJson.getString("msg");
throw new RuntimeException(string);
}
return base64;
}
}
4.EchartDto
@Data
@NoArgsConstructor
public class EchartsDto {
private String templatePath;
private Map<String, Object> datas;
private String template;
public EchartsDto(String template){
//在这里配置你的模板路径(模板文件下面会有几个)
this.templatePath = "";
//这个是你模板的文件名
this.template = template;
}
}
5.把文件保存到本地
/**
* 生成图表
* @param echarts
* @return
* @throws TemplateException
* @throws IOException
*/
public static String generateChart(EchartsDto echarts) throws TemplateException, IOException {
//传入EchartsDto
String option = FreemarkerUtil.generateString(echarts.getTemplate(), echarts.getTemplatePath(), echarts.getDatas());
String base64 = EchartsUtil.generateEchartsBase64(option);
//使用uuid 命名
String uuid = UUID.randomUUID().toString();
// 在这里的 ./是你项目的根目录
String filePath = "./out/" + uuid + ".png";
//生成本地图片
generateImage(base64, filePath);
return filePath;
}
public static void generateImage(String base64, String path) throws IOException {
BASE64Decoder decoder = new BASE64Decoder();
try (OutputStream out = new FileOutputStream(path)) {
byte[] b = decoder.decodeBuffer(base64);
for (int i = 0; i < b.length; ++i) {
if (b[i] < 0) {
b[i] += 256;
}
}
out.write(b);
out.flush();
}
}
五、模板以及测试
这里的传入数据都是自己封装的,具体情况可以看下echarts的官网以及里面的数据格式。
如果想直接测试用的话,可以先换成map
这里有两个注意的地方,单纯的字符串一定不能用json去转换,另一个是在模板里的字符串,一定要用单引号引起来
这里就先这么多了,其实都是大同小异,但是要注意一点,有的图表,官方的例子直接拿下来是用不了的,要把有些冗余的配置给删掉才可以,不然就会一直卡在请求上。建议是在官网上调整好了,然后把模复制到本地文件,注意不要最后的 “;” 和前面的哪些 只要 option=的大括号里面的内容
1.雷达图
radarOption.ftl
{
title: {
text: '${title}'
},
legend: {
data: ${legendData}
},
radar: {
indicator: ${indicator}
},
series: [
{
name: ${seriesName},
type: 'radar',
data:${data}
}
]
}
public static EchartsDto generateChart1Pkg(LeidaChart leidaChart) {
EchartsDto echarts = new EchartsDto("radarOption.ftl");
HashMap<String, Object> datas = new HashMap<>();
datas.put("title", leidaChart.getTitleText());
datas.put("legendData",JSON.toJSONString(leidaChart.getLegendData()));
datas.put("seriesName", JSON.toJSONString(leidaChart.getSeriesName()));
datas.put("data", JSON.toJSONString(leidaChart.getSeriesData()));
datas.put("indicator",JSON.toJSONString(leidaChart.getIndicator()));
echarts.setDatas(datas);
return echarts;
}
//这里由于乌龙事件,代码是上面的里面是int 类型,下面的是double类型,其实用double类型的就可以了
public static EchartsDto generateChart1Pkg(LeidaDoubleChart leidaChart) {
EchartsDto echarts = new EchartsDto("radarOption.ftl");
HashMap<String, Object> datas = new HashMap<>();
datas.put("title", leidaChart.getTitleText());
datas.put("legendData",JSON.toJSONString(leidaChart.getLegendData()));
datas.put("seriesName", JSON.toJSONString(leidaChart.getSeriesName()));
datas.put("data", JSON.toJSONString(leidaChart.getSeriesData()));
datas.put("indicator",JSON.toJSONString(leidaChart.getIndicator()));
echarts.setDatas(datas);
return echarts;
}
@Test
void generateChart1PkgTest() throws Exception {
LeidaChart leidaChart = new LeidaChart();
leidaChart.setTitleText("Basic Radar Chart");
leidaChart.setLegendData(Lists.newArrayList("1","2"));
leidaChart.setIndicator(Lists.newArrayList(new IndicatorData("Sales",6500),new IndicatorData("Administration",16000),new IndicatorData("Information Technology",30000)));
leidaChart.setSeriesName("Budget");
leidaChart.setSeriesData(Lists.newArrayList(new SeriesData("Allocated Budget",Lists.newArrayList(4200, 3000, 20000))));
GenerateChart.generateChart(GenerateChart.generateChart1Pkg(leidaChart));
}
2.柱状图
barOption.ftl
{
title: {
text:'${title}',
x:'middle',
textAlign:'center'
},
xAxis: {
type: 'category',
axisLabel: { show: true },
data: ${categories}
},
yAxis: {
type: 'value'
},
series: [{
data: ${values},
type: 'bar'
}]
}
public static EchartsDto generateChart2Pkg(ZhuChart zhuChart) {
EchartsDto echarts = new EchartsDto("barOption.ftl");
HashMap<String, Object> datas = new HashMap<>();
datas.put("categories", JSON.toJSONString(zhuChart.getXAxisData()));
datas.put("values", JSON.toJSONString(zhuChart.getSeriesData()));
datas.put("title","");
if(Objects.nonNull( zhuChart.getTitle())){
datas.put("title",zhuChart.getTitle());
}
echarts.setDatas(datas);
return echarts;
}
@Test
void generateChart2PkgTest() throws Exception {
ZhuChart zhuChart = new ZhuChart();
zhuChart.setTitle("水果");
zhuChart.setXAxisData(Lists.newArrayList( "苹果", "香蕉", "西瓜"));
zhuChart.setSeriesData(Lists.newArrayList(3, -2, 1));
System.out.println(GenerateChart.generateChart(GenerateChart.generateChart2Pkg(zhuChart)));
}
3.横向柱状图
{
title: {
text: '${title}'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
xAxis: {
type: 'value',
},
yAxis: {
type: 'category',
data: ${yaxis}
},
series: [
{
type: 'bar',
data: ${seriesData}
}
]
}
public static EchartsDto generateChart3Pkg(YZhuChart zhuChart) {
EchartsDto echarts = new EchartsDto("TBarOption.ftl");
HashMap<String, Object> datas = new HashMap<>();
datas.put("yaxis", JSON.toJSONString(zhuChart.getYaxis()));
datas.put("seriesData", JSON.toJSONString(zhuChart.getSeriesData()));
datas.put("title", zhuChart.getTitleText());
echarts.setDatas(datas);
return echarts;
}
@Test
void generateChart3PkgTest() throws Exception {
YZhuChart zhuChart = new YZhuChart();
zhuChart.setTitleText("World Population");
zhuChart.setYaxis(Lists.newArrayList( "Brazil", "Indonesia", "USA"));
zhuChart.setSeriesData(Lists.newArrayList(18203, 23489, 29034));
System.out.println(GenerateChart.generateChart(GenerateChart.generateChart3Pkg(zhuChart)));
}
4.饼状图
pieOption.ftl
{
title: {
text: '${titleText}',
},
series: [
{
type: 'pie',
data: ${seriesData},
}
]
}
public static EchartsDto generateChart4Pkg(PieChart pieChart) {
EchartsDto echarts = new EchartsDto("pieOption.ftl");
HashMap<String, Object> datas = new HashMap<>();
datas.put("seriesData", JSON.toJSONString(pieChart.getSeriesData()));
datas.put("titleText", pieChart.getTitleText());
echarts.setDatas(datas);
return echarts;
}
@Test
void generateChart4PkgTest() throws Exception {
PieChart pieChart = new PieChart();
pieChart.setTitleText("Referer of a Website");
pieChart.setSeriesData(Lists.newArrayList(new NameValuePair("Search Engine",1058),new NameValuePair("Direct",735)));
System.out.println(GenerateChart.generateChart(GenerateChart.generateChart4Pkg(pieChart)));
}
六、保存到pdf
public static void generatePdf() throws Exception {
//文件的输出路径
String outPutPath = "./out/pdf/report/";
//图片的地址
Document document = new Document(PageSize.A4, 50, 50, 30, 20);
File reportFile = new File(outPutPath);
if (!reportFile.exists()) {
reportFile.mkdirs();
}
String filePath = outPutPath + fileName + ".pdf";
File pdfFile = new File(filePath);
pdfFile.createNewFile();
PdfWriter.getInstance(document, new FileOutputStream(filePath));
document.open();
imgPath = GenerateChart.generateChart(echarts);
//使用段落加,会自己换行
Paragraph p = new Paragraph();
Image img = Image.getInstance(imgPath);
img.setAlignment(Image.LEFT| Image.TEXTWRAP);
//设置图片的大小(原大小(800,400))
img.scaleToFit(400,200);
//段落里用不了图片所以要使用chunk
p.add(new Chunk(img, 0, 0, true));
document.add(p);
//记得关闭
document.close();
}