SpringBoot纯后台生成Echarts图片(三)
一、项目工程结构
二.项目依赖说明
项目的pom.xml配置,属性配置 见第一篇
三.项目代码说明
(1)echarts-pojo模块(数据模型)
package com.lhf.springboot.echarts.pojo; import lombok.Data; /** * @ClassName: PieData * @Author: liuhefei * @Description: TODD */ @Data public class PieData { private String[] types; private String[] datas; private String title; }
(2)controler模块(API接口)
package com.lhf.springboot.controller; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.lhf.springboot.common.JsonResult; import com.lhf.springboot.echarts.pojo.BarData; import com.lhf.springboot.echarts.pojo.LinesData; import com.lhf.springboot.echarts.pojo.PieData; import com.lhf.springboot.util.*; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.apache.commons.lang3.time.FastDateFormat; import org.jboss.logging.Logger; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import java.awt.*; import java.io.File; import java.io.FileOutputStream; import java.text.DecimalFormat; import java.util.*; import java.util.List; /** * @ClassName: EchartsTemplateController * @Author: liuhefei * @Description: 使用模板生成饼图 */ @Api(value = "Freemarker模板生成Echarts图表Api接口", tags = "模板生成图表Api-1") @RequestMapping("/template") @RestController public class EchartsTemplateController { private final static Logger logger = Logger.getLogger(EchartsController.class); @Value("${img-url}") private String imgUrl; @Value("${img-url-path}") private String imgUrlPath; @Value("${request-url}") private String requestUrl; private static final String path = FreemarkerUtil.class.getClassLoader().getResource("").getPath(); private String temPath = path + "templates"; @ApiOperation("模板生成饼图, html文件格式") @RequestMapping(value = "/pie", method = RequestMethod.POST) public JsonResult createPieFtl(@RequestBody PieData pieData) { String title = pieData.getTitle(); String[] types = pieData.getTypes(); String[] datas = pieData.getDatas(); if (title == null || types == null || datas == null) { return new JsonResult(-1, "参数异常"); } if(types.length != datas.length){ return new JsonResult(-1, "数据不对应"); } //组装参数 Double data = null; Double sum = 0.0; DecimalFormat df = new DecimalFormat(".00"); List<String> stringList = new ArrayList<>(); String bfb = null; String typeParam = null; List<Map<String, Object>> listMap = new ArrayList<>(); for(int i = 0;i<types.length;i++){ data = Double.valueOf(datas[i]); System.out.println("data = " + data); sum += Double.valueOf(data); } System.out.println("sum = " + sum); for(int i =0 ;i<types.length;i++){ data = Double.valueOf(datas[i]); bfb = String.valueOf(df.format(data/sum * 100)); System.out.println("bfb = " + bfb); typeParam = types[i] + ":" + datas[i] + "(" + bfb + "%)"; System.out.println(typeParam); //1.组装types stringList.add(typeParam); //2.组装datas Map<String, Object> mapStr = new HashMap<>(); mapStr.put("name", typeParam); mapStr.put("value", datas[i]); listMap.add(mapStr); } System.out.println(stringList); System.out.println(listMap); try { //组装参数 HashMap<String, Object> pieDatas = new HashMap<>(); pieDatas.put("title", title); //pieDatas.put("types", JSON.toJSONString(types)); pieDatas.put("types", JSON.toJSONString(stringList)); pieDatas.put("datas", JSON.toJSONString(listMap)); String option = FreemarkerUtil.generateString("pieOption1.ftl", temPath, pieDatas); System.out.println("option = " + option); Map<String, Object> pieOption = new HashMap<>(); pieOption.put("option", option); String htmlPie = FreemarkerUtil.generateString("pieOption.ftl", temPath, pieOption); System.out.println("htmlPie = " + htmlPie); //写入html String nowStr = FastDateFormat.getInstance("yyyyMMddHHmmss").format(new Date()); String imageName = "pie"+nowStr; File htmlFile = new File(imgUrl+imageName+".html"); if(!htmlFile.exists()){ htmlFile.createNewFile(); } byte bytes[] = new byte[1024]; bytes = htmlPie.getBytes(); int b = bytes.length; //字节长度 FileOutputStream fos = new FileOutputStream(htmlFile); fos.write(bytes, 0, b); fos.write(bytes); fos.close(); } catch (Exception e) { logger.error("发生了异常," + e.getMessage()); return new JsonResult(-1, "Fail"); } return new JsonResult(1, "SUCCESS"); } @ApiOperation("模板生成饼图") @RequestMapping(value = "/pieImg", method = RequestMethod.POST) public JsonResult createPieFtlImage(@RequestBody PieData pieData) { String title = pieData.getTitle(); String[] types = pieData.getTypes(); String[] datas = pieData.getDatas(); if (title == null || types == null || datas == null) { return new JsonResult(-1, "参数异常"); } if(types.length != datas.length){ return new JsonResult(-1, "数据不对应"); } //组装参数 Double data = null; Double sum = 0.0; DecimalFormat df = new DecimalFormat(".00"); List<String> stringList = new ArrayList<>(); String bfb = null; String typeParam = null; List<Map<String, Object>> listMap = new ArrayList<>(); for(int i = 0;i<types.length;i++){ data = Double.valueOf(datas[i]); System.out.println("data = " + data); sum += Double.valueOf(data); } System.out.println("sum = " + sum); for(int i =0 ;i<types.length;i++){ data = Double.valueOf(datas[i]); bfb = String.valueOf(df.format(data/sum * 100)); System.out.println("bfb = " + bfb); typeParam = types[i] + ":" + datas[i] + "(" + bfb + "%)"; System.out.println(typeParam); //1.组装types stringList.add(typeParam); //2.组装datas Map<String, Object> mapStr = new HashMap<>(); mapStr.put("name", typeParam); mapStr.put("value", datas[i]); listMap.add(mapStr); } System.out.println(stringList); System.out.println(listMap); String oldFilePath = null; String newFilePath = null; try { //组装参数 HashMap<String, Object> pieDatas = new HashMap<>(); pieDatas.put("title", title); //pieDatas.put("types", JSON.toJSONString(types)); pieDatas.put("types", JSON.toJSONString(stringList)); pieDatas.put("datas", JSON.toJSONString(listMap)); String option = FreemarkerUtil.generateString("pieOption1.ftl", temPath, pieDatas); System.out.println("option = " + option); long nowStr = Calendar.getInstance().getTimeInMillis(); String imageName = "pie"+nowStr+ RandomUtils.getRandomString(4)+".png"; Map<String, Object> opt = (Map<String, Object>) JSONObject.parse(option); PhantomJS js = new PhantomJS(); js.setOpt(opt); js.setReqMethod("echarts"); js.setFile(imgUrlPath+imageName); PhantomJSUtil.phantomJS(requestUrl, JSON.parseObject(JSON.toJSONString(js))); oldFilePath = imgUrlPath+imageName; newFilePath = imgUrlPath+"new"+imageName; logger.info("oldFilePath = " + oldFilePath); logger.info("newFilePath = " + newFilePath); String logoText = "霜花似雪"; //添加水印 ImageUtil.markImageByText(logoText, oldFilePath, newFilePath, -15, new Color(190,190,190), "png"); } catch (Exception e) { logger.error("发生了异常," + e.getMessage()); return new JsonResult(-1, "Fail"); } return new JsonResult(1, "SUCCESS"); } }
三、插件环境配置说明
插件环境说明见第一篇文章
其他重要说明:
生成饼图的命令:
D:\>cd D:\softpack\echarts\phantomjs-2.1.1-windows\bin D:\softpack\echarts\phantomjs-2.1.1-windows\bin>phantomjs D:\softpack\echarts\phantomjs-2.1.1-windows\bin\echarts-util.js -s -p 6668 或者phantomjs D:\softpack\echarts\phantomjs-2.1.1-windows\bin\echarts-util-one.js -s -p 6668
关键文件说明:
echarts-util.js: 此文件用于生成状图、折线图、饼图, 只限于普通的option(option中没有js方法的)
echarts-util-one.js: 此文件用于生成状图、折线图、饼图,生成的柱状图具有颜色渐变的效果,option中含有js方法,实例可以参考bar.txt文件
echarts-util.js代码:
phantom.outputEncoding = "gbk";// 为防止输出中文时出现乱码,可设置输出编码格式,写在最顶部 var params = require('system');// 获取系统参数 var server = require('webserver').create(); // 服务端 var port = params.args[3];// 端口,与启动命令有关,不一定是3 var listen = server.listen(port, function(request, response) {// 监听端口 var args = serverGetArgs(request);// 得到网络请求参数 args.response = response; methodDis(args); }); var jslib = { jquery : phantom.libraryPath + '/lib/jquery-3.2.1.min.js', echarts : phantom.libraryPath + '/lib/echarts.min.js', china : phantom.libraryPath + '/lib/china.js', }; /** * 请求分发 * * @author liansh * @data 2019年9月19日 下午11:32:59 * @param args */ function methodDis(args) { if (args.reqMethod == "table") { table(args); } else if (args.reqMethod == "echarts") { echarts(args); } if (args.exit == "true") { writeResponse(args.response, { error_no : 0 }); phantom.exit(); } } function table(args) { var page = require('webpage').create();// 打开页面 // 设置分辨率 page.viewportSize = { width : 1000, height : 1200 }; // 打开页面 page.open(args.url || 'http://127.0.0.1:8080/hello', function(status) { if (status == "fail") { writeResponse(args.response, { error_no : -1 }); return; } page.injectJs(jslib.jquery); var tableheight = page.evaluate(function() { return $('body').height() + 20; }); // 定义剪切范围 page.clipRect = { top : 0, left : 0, width : 1000, height : tableheight }; // var base64 = 'data:image/png;base64,' + page.renderBase64('png'); page.render(args.file);// 将整个page保存为文件,可以是png,jpg, gif,pdf page.close(); writeResponse(args.response, { error_no : 0 }); }); page.onError = function(msg, trace) { writeResponse(args.response, { error_no : -1, error_info : trace }); }; } function echarts(args) { var page = require('webpage').create(); // 客户端 page.open("about:blank", function(status) {// 空白页 page.injectJs(jslib.jquery); page.injectJs(jslib.echarts); page.injectJs(jslib.china); var pageBody = page.evaluate(function(args) { // 动态加载js,获取options数据 $('<script>').attr('type', 'text/javascript').html('var options = ' // + JSON.stringify(args.opt)).appendTo(document.head); // 取消动画,否则生成图片过快,会出现无数据 if (options !== undefined) { options.animation = false; } // body背景设置为白色 $(document.body).css('backgroundColor', 'white'); // echarts容器 var container = $("<div>").attr('id', 'container').css({ width : args.width, height : args.height }).appendTo(document.body); var eChart = echarts.init(container[0]); eChart.setOption(options); }, args); // 定义剪切范围 page.clipRect = { top : 0, left : 0, width : args.width - 100, height : args.height + 10 }; // var base64 = 'data:image/png;base64,' + page.renderBase64('png'); // writeResponse(args.response, {// 返回给http请求 // error_no : 0, // base64 : base64 // }); page.render(args.file);// 将整个page保存为文件,可以是png,jpg, gif,pdf page.close(); writeResponse(args.response, { error_no : 0 }); }); page.onError = function(msg, trace) { writeResponse(args.response, { error_no : -1, error_info : trace }); }; } function writeResponse(response, msg) { response.write(JSON.stringify(msg || { error_no : 0 })); response.close(); } /** * 获取请求参数 * * @author liansh * @data 2019年9月19日 下午11:27:16 * @param request * @returns */ function serverGetArgs(request) { var args = {}; if ('GET' === request.method) { var index = request.url.indexOf('?'); if (index !== -1) { pairs = request.url.substr(index + 1).split("&"); for (var i = 0; i < pairs.length; i++) { var pos = pairs[i].indexOf('='); if (pos === -1) continue; var key = pairs[i].substring(0, pos); var value = pairs[i].substring(pos + 1); // 中文解码,必须写两层 value = decodeURIComponent(decodeURIComponent(value)); args[key] = value; } } } else if ('POST' === request.method) { args = JSON.parse(request.post); } args.width = args.width || 1000; args.height = args.height || 400; return args; };
echarts-util-one.js代码:
phantom.outputEncoding = "gbk";// 为防止输出中文时出现乱码,可设置输出编码格式,写在最顶部 var params = require('system');// 获取系统参数 var server = require('webserver').create(); // 服务端 var port = params.args[3];// 端口,与启动命令有关,不一定是3 var listen = server.listen(port, function(request, response) {// 监听端口 var args = serverGetArgs(request);// 得到网络请求参数 args.response = response; methodDis(args); }); var jslib = { jquery : phantom.libraryPath + '/lib/jquery-3.2.1.min.js', echarts : phantom.libraryPath + '/lib/echarts.min.js', china : phantom.libraryPath + '/lib/china.js', }; /** * 请求分发 * * @author liansh * @data 2019年9月19日 下午11:32:59 * @param args */ function methodDis(args) { if (args.reqMethod == "table") { table(args); } else if (args.reqMethod == "echarts") { echarts(args); } if (args.exit == "true") { writeResponse(args.response, { error_no : 0 }); phantom.exit(); } } function table(args) { var page = require('webpage').create();// 打开页面 // 设置分辨率 page.viewportSize = { width : 1000, height : 1200 }; // 打开页面 page.open(args.url || 'http://127.0.0.1:8080/hello', function(status) { if (status == "fail") { writeResponse(args.response, { error_no : -1 }); return; } page.injectJs(jslib.jquery); var tableheight = page.evaluate(function() { return $('body').height() + 20; }); // 定义剪切范围 page.clipRect = { top : 0, left : 0, width : 1000, height : tableheight }; // var base64 = 'data:image/png;base64,' + page.renderBase64('png'); page.render(args.file);// 将整个page保存为文件,可以是png,jpg, gif,pdf page.close(); writeResponse(args.response, { error_no : 0 }); }); page.onError = function(msg, trace) { writeResponse(args.response, { error_no : -1, error_info : trace }); }; } function echarts(args) { var page = require('webpage').create(); // 客户端 page.open("about:blank", function(status) {// 空白页 /** * 报错{"file":"undefined","line":3,"function":""},{"file":"","line":18,"function":""} * "file":"undefined" 为所引用的jslib文件路径不对,需要重新检查路径 */ page.injectJs(jslib.jquery); page.injectJs(jslib.echarts); page.injectJs(jslib.china); var pageBody = page.evaluate(function(args) { // 动态加载js,获取options数据 //var itemStyle = '{"normal":{"color" : new echarts.graphic.LinearGradient(0, 0, 0, 1, [ '+ '{offset : 0,color : "#83bff6"}, {offset : 0.5,color : "#188df0"}, {offset : 1,color : "#188df0"} ])}}'; var itemStyle = '{"normal":{"color" : new echarts.graphic.LinearGradient(0, 0, 0, 1, [ '+ '{offset : 0,color : "#3370FE"}, {offset : 0.5,color : "#3370FE"}, {offset : 1,color : "#3370FE"} ])}}'; // 动态加载js,获取options数据 $('<script>').attr('type', 'text/javascript').html('var options =' // + JSON.stringify(args.opt).replace('\"__itemStyle"', itemStyle)).appendTo(document.head); // 取消动画,否则生成图片过快,会出现无数据 if (options !== undefined) { options.animation = false; } // body背景设置为白色 $(document.body).css('backgroundColor', 'white'); // echarts容器 var container = $("<div>").attr('id', 'container').css({ width : args.width, height : args.height }).appendTo(document.body); var eChart = echarts.init(container[0]); eChart.setOption(options); }, args); // 定义剪切范围 page.clipRect = { top : 0, left : 0, width : args.width - 100, height : args.height + 10 }; // var base64 = 'data:image/png;base64,' + page.renderBase64('png'); // writeResponse(args.response, {// 返回给http请求 // error_no : 0, // base64 : base64 // }); page.render(args.file);// 将整个page保存为文件,可以是png,jpg, gif,pdf page.close(); writeResponse(args.response, { error_no : 0 }); }); page.onError = function(msg, trace) { writeResponse(args.response, { error_no : -1, error_info : trace }); }; } function writeResponse(response, msg) { response.write(JSON.stringify(msg || { error_no : 0 })); response.close(); } /** * 获取请求参数 * * @author liansh * @data 2019年9月19日 下午11:27:16 * @param request * @returns */ function serverGetArgs(request) { var args = {}; if ('GET' === request.method) { var index = request.url.indexOf('?'); if (index !== -1) { pairs = request.url.substr(index + 1).split("&"); for (var i = 0; i < pairs.length; i++) { var pos = pairs[i].indexOf('='); if (pos === -1) continue; var key = pairs[i].substring(0, pos); var value = pairs[i].substring(pos + 1); // 中文解码,必须写两层 value = decodeURIComponent(decodeURIComponent(value)); args[key] = value; } } } else if ('POST' === request.method) { args = JSON.parse(request.post); } args.width = args.width || 1000; args.height = args.height || 400; return args; };
四、结合Swagger文档测试
环境配置完成之后,启动服务,swagger文档:http://localhost:8095/swagger-ui.html
测试数据:
{ "datas": [ 43364, 13899, 12000, 2181, 21798, 1796, 1300 ], "title": "胸罩图例", "types": [ "A罩杯", "B罩杯", "C罩杯", "D罩杯", "E罩杯", "F罩杯","G罩杯" ] }
效果展示:
(1)生成饼图,html文件格式
<!DOCTYPE html> <html style="height: 100%"> <head> <meta charset="utf-8"> </head> <body style="height: 100%; margin: 0"> <div id="container" style="height: 100%"></div> <script type="text/javascript" class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="http://echarts.baidu.com/gallery/vendors/echarts/echarts.min.js"></script> <script type="text/javascript" class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="http://echarts.baidu.com/gallery/vendors/echarts-gl/echarts-gl.min.js"></script> <script type="text/javascript" class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="http://echarts.baidu.com/gallery/vendors/echarts-stat/ecStat.min.js"></script> <script type="text/javascript" class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="http://echarts.baidu.com/gallery/vendors/echarts/extension/dataTool.min.js"></script> <script type="text/javascript" class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="http://echarts.baidu.com/gallery/vendors/simplex.js"></script> <script type="text/javascript"> var dom = document.getElementById("container"); var myChart = echarts.init(dom); var app = {}; option = null; option ={ "calculable": true, "legend": { "data": ["A罩杯:43364(45.01%)","B罩杯:13899(14.43%)","C罩杯:12000(12.46%)","D罩杯:2181(2.26%)","E罩杯:21798(22.63%)","F罩杯:1796(1.86%)","G罩杯:1300(1.35%)"], "orient": "vertical", "x": "left", "textStyle": { "color": "red", "fontSize": 15, "fontWeight": "bolder" } }, "series": [{ "center": ["50%", "60%"], "data": [{"name":"A罩杯:43364(45.01%)","value":"43364"},{"name":"B罩杯:13899(14.43%)","value":"13899"},{"name":"C罩杯:12000(12.46%)","value":"12000"},{"name":"D罩杯:2181(2.26%)","value":"2181"},{"name":"E罩杯:21798(22.63%)","value":"21798"},{"name":"F罩杯:1796(1.86%)","value":"1796"},{"name":"G罩杯:1300(1.35%)","value":"1300"}], "name": "胸罩图例", "radius": "65%", "type": "pie", "avoidLabelOverlap": true, "label": { "normal": { "show": true, "position": "top", "textStyle": { "color":"red", "fontSize": "15", "fontWeight": "bold" } }, "emphasis": { "show": true, "textStyle": { "fontSize": "20", "fontWeight": "bold" } } }, "labelLine": { "normal": { "show": true } } }], "title": { "subtext": "", "text": "胸罩图例", "x": "center", "textStyle": { "color": "green", "fontSize": 20, "fontWeight": "bolder" } }, "toolbox": { "feature": { "mark": { "lineStyle": { "color": "#1e90ff", "type": "dashed", "width": 2 }, "show": true }, "dataView": { "lang": ["数据视图", "关闭", "刷新"], "readOnly": false, "show": true, "title": "数据视图" }, "magicType": { "show": true, "title": { "bar": "柱形图切换", "stack": "堆积", "tiled": "平铺", "line": "折线图切换" }, "type": ["pie", "funnel"] }, "restore": { "show": true, "title": "还原" }, "saveAsImage": { "lang": ["点击保存"], "show": true, "title": "保存为图片", "type": "png" } }, "show": true }, "tooltip": { "formatter": "{a} <br/>{b} : {c} ({d}%)", "trigger": "item" } } ; if (option && typeof option === "object") { myChart.setOption(option, true); } </script> </body> </html>
(2)生成饼图,图片格式,效果图如下
不带水印:
带水印:
更多关于Echarts图表的使用请读者自行研究,如果遇到问题,欢迎与我交流。需要代码的话,也可以找我!
喜欢的话,请点个赞,感谢你的支持!
点击查看更多内容
2人点赞
评论
共同学习,写下你的评论
评论加载中...
作者其他优质文章
正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦