java后端使用phantomjs生成echarts图片

很多时候需要自动生成一些文档,并在文档中插入一些图片,一般B/S系统中图片由Echarts生成,但文档在java后台生成,这时怎样在后台调用Echarts的功能呢?答案是phantomJS。

1.安装phantomJS

下载地址:https://bitbucket.org/ariya/phantomjs/downloads/,国内镜像地址:http://npm.taobao.org/dist/phantomjs/
有各种版本的,我这里以windows为例。

2.下载EchartsConvert

下载地址:https://gitee.com/saintlee/echartsconvert

3.运行phantomJS

可以使用两种方式生成图片,直接在本地调用命令行或者使用远程服务,下面以远程服务为例。

在phantomJS解压后的bin目录中通过命令行运行phantomjs.exe <echarts-convert的路径> -s -p <端口>
如下图所示:
在这里插入图片描述

4.引入依赖

通过maven或gradle引入httpclient、freemark和fastjson的依赖,我这里略过了

5.httpclient工具类

package com.iscas.base.biz.util.echarts;

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;
    }
}

6.freemarker工具类

package com.iscas.base.biz.util.echarts;

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;
import org.apache.commons.lang3.StringUtils;

/**
 * freemarker工具类
 *
 * @author zhuquanwen
 * @vesion 1.0
 * @date 2021/11/24
 * @since jdk1.8
 */
public class FreemarkerUtils {


    public static String generateString(String templateFileName, String templateDirectoryPath, Map<String, Object> datas)
            throws IOException, TemplateException {
        Configuration configuration = new Configuration(Configuration.VERSION_2_3_0);

        // 设置默认编码
        configuration.setDefaultEncoding("UTF-8");

        if (templateDirectoryPath.startsWith("classpath:")) {
            //设置类加载器
            configuration.setClassLoaderForTemplateLoading(Thread.currentThread().getContextClassLoader(),
                    StringUtils.substringAfter(templateDirectoryPath, "classpath:"));
        } else {
            // 设置模板所在文件夹
            configuration.setDirectoryForTemplateLoading(new File(templateDirectoryPath));
        }

        // 生成模板对象
        Template template = configuration.getTemplate(templateFileName);

        // 将datas写入模板并返回
        try (StringWriter stringWriter = new StringWriter()) {
            template.process(datas, stringWriter);
            stringWriter.flush();
            return stringWriter.getBuffer().toString();
        }
    }
}

7.图像工具类

public class ImageUtils {
    private ImageUtils() {
    }

    /**
     * 将base64转为图片
     * */
    public static void convertBase64ToImage(String base64, OutputStream os) throws IOException {
        Base64.Decoder decoder = Base64.getDecoder();
        // 解密
        byte[] b = decoder.decode(base64);
        for (int i = 0; i < b.length; ++i) {
            if (b[i] < 0) {
                b[i] += 256;
            }
        }
        os.write(b);
        os.flush();
    }
}

8.Echarts工具类

package com.iscas.base.biz.util.echarts;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.http.client.ClientProtocolException;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * 参考:https://www.jianshu.com/p/dfc28fd7d786
 *
 * @author zhuquanwen
 * @vesion 1.0
 * @date 2021/11/24 21:00
 * @since jdk1.8
 */
public class EchartsUtils {
    private static final String SUCCESS_CODE = "1";

    public static String generateEchartsBase64(String phantomjsUrl, 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(phantomjsUrl, 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;
    }
}

9.模板opt.ftl

{
    title: {
        text:'${title}',
        x:'middle',
        textAlign:'center'
    },
    xAxis: {
        type: 'category',
        data: ${categories}
    },
    yAxis: {
        type: 'value'
    },
    series: [{
        data: ${values},
        type: 'bar'
    }]
}

10.测试

public class EchartTests {
    private static String url = "http://localhost:7777";
    @Test
    public void test() throws TemplateException, IOException {
        // 变量
        String title = "销量";
        String[] categories = new String[] { "2019", "2020", "2021" };
        int[] values = new int[] { 100, 105, 200 };

        // 模板参数
        HashMap<String, Object> datas = new HashMap<>();
        datas.put("categories", JSON.toJSONString(categories));
        datas.put("values", JSON.toJSONString(values));
        datas.put("title", title);

        // 生成option字符串
//        String option = FreemarkerUtils.generateString("option.ftl", "C:\\ideaProjects\\newframe\\biz-base\\src\\test\\java\\com\\iscas\\base\\biz\\util\\echarts", datas);
        String option = FreemarkerUtils.generateString("option.ftl", "classpath:ftl", datas);

        // 根据option参数
        String base64 = EchartsUtils.generateEchartsBase64(url, option);

        System.out.println("BASE64:" + base64);

        File test = File.createTempFile("test", ".png");
        test.deleteOnExit();
        try (OutputStream os = new FileOutputStream(test)) {
            ImageUtils.convertBase64ToImage(base64, os);
        }
        System.out.println("测试完成");
    }


}

11.生成的图片

在这里插入图片描述

12.生成折线图

上面是一个生成生成简单柱状图的例子,下面我们自定义一个折线图模板,生成一个折线图
折线图模板line.ftl

{
    "title": {
        "text": "${title}",
        "x": "left"
    },
    "tooltip": {
        "trigger": "axis"
    },
    "legend": {
        "data": ${legendData}
    },
    "xAxis": [
        {
            "boundaryGap": false,
            "type": "category",
            "data": ${xAxisData}
        }
    ],
    "yAxis": ${yAxis},
    "series": ${data}
}

测试:

 //测试折线图
    @Test
    public void testLine() throws TemplateException, IOException {
        // 标题
        String title = "资源增长情况";
        //legendData
        String[] legendData = new String[]{"ECS", "实例", "CPU", "MEM"};
        //xAxisData
        String[] xAxisData = new String[]{"2021-10-01", "2021-10-02", "2021-10-03", "2021-10-04", "2021-10-05"};
        //yAxis
        ArrayList<Map<String, Object>> yAxis = new ArrayList<>(){{
            add(new HashMap<>(){{
                put("type", "value");
                put("position", "left");
                put("name", "台");
            }});
            add(new HashMap<>(){{
                put("type", "value");
                put("position", "right");
                put("name", "使用量");
            }});
        }};
        //seriesData
        ArrayList<Map<String, Object>> series = new ArrayList<>(){{
            add(new HashMap<>(){{
                put("name", "ECS");
                put("type", "line");
                put("data", new int[]{100, 200, 210, 180, 260});
            }});
            add(new HashMap<>(){{
                put("name", "实例");
                put("type", "line");
                put("data", new int[]{320, 220, 268, 320, 330});
            }});
            add(new HashMap<>(){{
                put("name", "CPU");
                put("type", "line");
                put("data", new int[]{120, 110, 121, 110, 160});
            }});
            add(new HashMap<>(){{
                put("name", "MEM");
                put("type", "line");
                put("data", new int[]{320, 23, 65, 190, 285});
            }});
        }};

        // 模板参数
        HashMap<String, Object> datas = new HashMap<>();
        datas.put("xAxisData", JSON.toJSONString(xAxisData));
        datas.put("legendData", JSON.toJSONString(legendData));
        datas.put("title", title);
        datas.put("yAxis", JSON.toJSONString(yAxis));
        datas.put("data", JSON.toJSONString(series));


        // 生成option字符串
        String option = FreemarkerUtils.generateString("line.ftl", "classpath:ftl", datas);

        // 根据option参数
//        option = "{\"title\":{\"text\":\"资源增长情况\",\"x\":\"left\"},\"toolbox\":{\"feature\":{\"saveAsImage\":{\"show\":true,\"title\":\"保存为图片\",\"type\":\"png\",\"lang\":[\"点击保存\"]}},\"show\":true},\"tooltip\":{\"trigger\":\"axis\"},\"legend\":{\"data\":[\"ECS\",\"实例\",\"CPU\",\"MEM\"]},\"xAxis\":[{\"boundaryGap\":false,\"type\":\"category\",\"data\":[\"2019-03-09\",\"2019-03-02\",\"2019-03-16\"]}],\"yAxis\":[{\"type\":\"value\",\"position\":\"left\",\"name\":\"ECS台\",\"axisLine\":{\"lineStyle\":{\"color\":\"#1E90FF\"}}},{\"type\":\"value\",\"position\":\"left\",\"name\":\"容器实例台\",\"axisLine\":{\"lineStyle\":{\"color\":\"#8A2BE2\"}}}],\"series\":[{\"name\":\"ECS\",\"type\":\"line\",\"stack\":\"总量\",\"data\":[120,132,101,134,90,230,210]},{\"name\":\"实例\",\"type\":\"line\",\"stack\":\"总量\",\"data\":[220,182,191,234,290,330,310]},{\"name\":\"CPU\",\"type\":\"line\",\"stack\":\"总量\",\"data\":[150,232,201,154,190,330,410]},{\"name\":\"MEM\",\"type\":\"line\",\"stack\":\"总量\",\"data\":[150,232,201,154,190,330,410]}]}" ;

        String base64 = EchartsUtils.generateEchartsBase64(url, option);

        System.out.println("BASE64:" + base64);

        File test = File.createTempFile("test", ".png");
        test.deleteOnExit();
        try (OutputStream os = new FileOutputStream(test)) {
            ImageUtils.convertBase64ToImage(base64, os);
        }
        System.out.println("测试完成");
    }

生成的图片:
在这里插入图片描述

13.Java使用Echarts工具生成option

除了上面的使用freemarker的方式生成option,还有一种方式是使用Echarts工具生成。

(1)首先引入Echarts依赖

gradle:

compile group: 'com.github.abel533', name: 'ECharts', version: '3.0.0.6'

maven:

<dependency>
    <groupId>com.github.abel533</groupId>
    <artifactId>ECharts</artifactId>
    <version>3.0.0.6</version>
</dependency>

(2)生成option

生成时可以照着echarts官网的js来生成,基本是一一对应的

    private String createPie() {
        List<Map<String, Object>> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            Map<String, Object> map = new HashMap<>();
            map.put("NAME", "张三" + i);
            map.put("TOTAL", new Random().nextInt(50));
            list.add(map);
        }
        //创建Option
        Option option = new GsonOption();
        option.title(new Title().text("测试饼图").x("middle"));
        option.tooltip(Trigger.item);
        option.legend(new Legend().orient(Orient.vertical).left("left"));
        //饼图数据
        Pie pie = new Pie("测试饼图");
        //循环数据
        for (Map<String, Object> objectMap : list) {
            //饼图数据
            pie.data(new PieData(objectMap.get("NAME").toString(), objectMap.get("TOTAL")));
        }
        //设置数据
        option.series(pie);
        String optionStr = option.toString().replace(" ", "");
        return optionStr;
    }

(3)生成图片

    //测试饼状图
    @Test
    public void testPie() throws IOException {

        String option = createPie();

        String base64 = EchartsUtils.generateEchartsBase64(url, option);

        System.out.println("BASE64:" + base64);

        File test = File.createTempFile("test", ".png");
        test.deleteOnExit();
        try (OutputStream os = new FileOutputStream(test)) {
            ImageUtils.convertBase64ToImage(base64, os);
        }
        System.out.println("测试完成");
    }

(4) 生成的图片

在这里插入图片描述


版权声明:本文为u011943534原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。