如何在Java后臺生成Echarts圖片

一、準(zhǔn)備環(huán)境(主要以Centos7環(huán)境為準(zhǔn),通過Phantomjs和EChartsConvert工具實(shí)現(xiàn)具體功能)

1、下載Phantomjs
2、部署Phantomjs到服務(wù)器
(1)、將從官網(wǎng)下載的包phantomjs-2.1.1-linux-x86_64.tar.bz2上傳到服務(wù)器/usr/local/phantomjs目錄之下,通過以下命令進(jìn)行解壓
tar -jxvf phantomjs-2.1.1-linux-x86_64.tar.bz2

解壓時可能會出現(xiàn)以下錯誤提示

tar (child): bzip2: Cannot exec: No such file or directory
tar (child): Error is not recoverable: exiting now
tar: Child returned status 2
tar: Error is not recoverable: exiting now

出現(xiàn)該錯誤,使用以下命令安裝bzip2插件,安裝完成后再解壓

yum install -y bzip2

完成解壓后進(jìn)入/usr/local/phantomjs/phantomjs-2.1.1-linux-x86_64/bin目錄,通過./phantomjs -v 命令,查看phantomjs 是否能使用,如果能正常使用,則會輸出對應(yīng)phantomjs版本號,但此處提示以下錯誤信息

./phantomjs: error while loading shared libraries: libfontconfig.so.1: cannot open shared object file: No such file or directory

提示該錯誤信息, 需要安裝安裝fontconfigfreetype依賴,其實(shí)phantomjs界面有對應(yīng)的提示,運(yùn)行phantomjs需要fontconfig依賴,通過以下命令,安裝依賴

yum install fontconfig freetype2

安裝依賴完成之后,再到/usr/local/phantomjs/phantomjs-2.1.1-linux-x86_64/bin目錄下,執(zhí)行./phantomjs -v 命令,可看到如下內(nèi)容,則說明安裝成功

image.png

(2)、將/usr/local/phantomjs/phantomjs-2.1.1-linux-x86_64/bin添加到環(huán)境變量,將export PATH=$PATH:/usr/local/phantomjs/phantomjs-2.1.1-linux-x86_64/bin放到profile文件最后一行,最后重新加載環(huán)境變量
vi /etc/profile

export PATH=$PATH:/usr/local/phantomjs/phantomjs-2.1.1-linux-x86_64/bin

source /etc/profile
(3)、安裝字體(一定要進(jìn)行字體安裝,否則會導(dǎo)致導(dǎo)出的圖片中文無法正常顯示)
yum install bitmap-fonts bitmap-fonts-cjk
3、下載EChartsConvert并運(yùn)行項(xiàng)目

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

1、將下載好的包上傳到服務(wù)器;
2、在echarts-convert.js同級目錄下,運(yùn)行命令phantomjs echarts-convert.js -s
3、 如果控制臺出現(xiàn)echarts-convert server start success. [pid]=xxxx則表示啟動成功,默認(rèn)端口9090;
4、也可以通過phantomjs echarts-convert.js -s -p 8080的方式,直接指定端口號

二、在項(xiàng)目中添加以下Maven依賴

        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.23</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.48</version>
        </dependency>

        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.12</version>
        </dependency>

三、編寫工具類

(1)、Http工具類
package com.framework.pie.poi.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 = "";

        // 創(chuàng)建CloseableHttpClient對象
        CloseableHttpClient client = HttpClients.createDefault();

        // 創(chuàng)建post方式請求對象
        HttpPost httpPost = new HttpPost(url);

        // 生成請求參數(shù)
        List<NameValuePair> nameValuePairs = new ArrayList<>();
        if (params != null) {
            for (Entry<String, String> entry : params.entrySet()) {
                nameValuePairs.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
            }
        }

        // 將參數(shù)添加到post請求中
        httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs, charset));

        // 發(fā)送請求,獲取結(jié)果(同步阻塞)
        CloseableHttpResponse response = client.execute(httpPost);

        // 獲取響應(yīng)實(shí)體
        HttpEntity entity = response.getEntity();
        if (entity != null) {
            // 按指定編碼轉(zhuǎn)換結(jié)果實(shí)體為String類型
            responseEntity = EntityUtils.toString(entity, charset);
        }

        // 釋放資源
        EntityUtils.consume(entity);
        response.close();

        return responseEntity;
    }
}
(2)、Freemarker工具類
package com.framework.pie.poi.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;

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

        // 設(shè)置默認(rèn)編碼
        configuration.setDefaultEncoding("UTF-8");

        // 設(shè)置模板所在文件夾
        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工具類
package com.framework.pie.poi.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://192.168.199.138:9090";
    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字符串作為參數(shù)發(fā)送給echartsConvert服務(wù)器
        Map<String, String> params = new HashMap<>();
        params.put("opt", option);
        String response = HttpUtil.post(url, params, "utf-8");

        // 解析echartsConvert響應(yīng)
        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)、柱形圖option.ftl(用的springboot項(xiàng)目,直接將模板放置到resources中template文件夾中)
{
    title: {
        text:'${title}',
        x:'middle',
        textAlign:'center'
    },
    xAxis: {
        type: 'category',
        data: ${categories}
    },
    yAxis: {
        type: 'value'
    },
    series: [{
        data: ${values},
        type: 'bar'
    }]
}

折線圖

{
    title: {
        text: '折線圖測試'
    },
    tooltip : {
        trigger: 'axis'
    },
    legend: {
        data: ['國家級', '省級', '州市級']
    },
    xAxis: [
        {
            type: 'category',
            boundaryGap: false,
            data: ['2019上半年', '2019下半年', '2020上半年', '2020下半年', '2021上半年', '2021下半年'],
            axisLabel: {
                interval:0,
                rotate:30,
            }
        }
    ],
    yAxis: [
        {
            type: 'value'
        }
    ],
    series: [
        {
            name: '國家級',
            type: 'line',
            stack: 'Total',
            data: [120, 132, 101, 134, 90, 230, 210]
        },
        {
            name: '省級',
            type: 'line',
            stack: 'Total',
            data: [220, 182, 191, 234, 290, 330, 310]
        },
        {
            name: '州市級',
            type: 'line',
            stack: 'Total',
            data: [150, 232, 201, 154, 190, 330, 410]
        }
    ]
}

餅圖

{
    tooltip: {
        trigger: 'item',
        formatter: '{a} <br/>: {c} (u0z1t8os%25)'
    },
    legend: {
        orient: 'vertical',
        left: 10,
        data: ['直接訪問', '郵件營銷', '聯(lián)盟廣告', '視頻廣告', '搜索引擎']
    },
    series: [
        {
            name: '訪問來源',
            type: 'pie',
            radius: ['50%25', '70%25'],
            avoidLabelOverlap: false,
            label: {
                show: false,
                position: 'center'
            },
            emphasis: {
                label: {
                    show: true,
                    fontSize: '30',
                    fontWeight: 'bold'
                }
            },
            labelLine: {
                show: false
            },
            data: [
                {value: 335, name: '直接訪問'},
                {value: 310, name: '郵件營銷'},
                {value: 234, name: '聯(lián)盟廣告'},
                {value: 135, name: '視頻廣告'},
                {value: 1548, name: '搜索引擎'}
            ]
        }
    ]
}
(5)、測試類信息
package com.framework.pie.poi.echarts;

import com.alibaba.fastjson.JSON;
import freemarker.template.TemplateException;
import org.apache.http.client.ClientProtocolException;
import sun.misc.BASE64Decoder;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;

public class TestEcharts02 {
    public static void main(String[] args) throws ClientProtocolException, IOException, TemplateException {
        // 變量
        String title = "水果";
        String[] categories = new String[] { "蘋果", "香蕉", "西瓜" };
        int[] values = new int[] { 3, 2, 1 };

        // 模板參數(shù)
        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 = FreemarkerUtil.generateString("option.ftl", "template/echarts", datas);

        // 根據(jù)option參數(shù)
        String base64 = EchartsUtil.generateEchartsBase64(option);

        System.out.println("BASE64:" + base64);
        generateImage(base64, "E:/export/echarts001.png");
    }

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

四、效果圖

echarts001.png
echarts6.png
echarts8.png

五、需要注意的問題

餅圖繪制不了,request時就卡住的問題,程序一直卡在如下界面,不會往下執(zhí)行


image.png

此處并不是餅圖繪制不了,而是只要opt中含有’%‘都會掛,原因是作者在代碼里執(zhí)行了兩次decodeURIComponent(詳情參考echarts-convert.js源碼259行),所以’%‘傳遞時也必需encode兩次,否則會造成%后的json串無法被decode導(dǎo)致卡住的問題。
此處可以將’%‘替換為’%25’解決,或是改源碼將decodeURIComponent改為一次,暫時沒有發(fā)現(xiàn)改為一次decode會出現(xiàn)中文問題

  • 餅圖問題模板
{
    tooltip: {
        trigger: 'item',
        formatter: '{a} <br/>: {c} (u0z1t8os%)'
    },
    legend: {
        orient: 'vertical',
        left: 10,
        data: ['直接訪問', '郵件營銷', '聯(lián)盟廣告', '視頻廣告', '搜索引擎']
    },
    series: [
        {
            name: '訪問來源',
            type: 'pie',
            radius: ['50%', '70%'],
            avoidLabelOverlap: false,
            label: {
                show: false,
                position: 'center'
            },
            emphasis: {
                label: {
                    show: true,
                    fontSize: '30',
                    fontWeight: 'bold'
                }
            },
            labelLine: {
                show: false
            },
            data: [
                {value: 335, name: '直接訪問'},
                {value: 310, name: '郵件營銷'},
                {value: 234, name: '聯(lián)盟廣告'},
                {value: 135, name: '視頻廣告'},
                {value: 1548, name: '搜索引擎'}
            ]
        }
    ]
}
  • 修正后模板(將%用%25替換)
{
    tooltip: {
        trigger: 'item',
        formatter: '{a} <br/>: {c} (u0z1t8os%25)'
    },
    legend: {
        orient: 'vertical',
        left: 10,
        data: ['直接訪問', '郵件營銷', '聯(lián)盟廣告', '視頻廣告', '搜索引擎']
    },
    series: [
        {
            name: '訪問來源',
            type: 'pie',
            radius: ['50%25', '70%25'],
            avoidLabelOverlap: false,
            label: {
                show: false,
                position: 'center'
            },
            emphasis: {
                label: {
                    show: true,
                    fontSize: '30',
                    fontWeight: 'bold'
                }
            },
            labelLine: {
                show: false
            },
            data: [
                {value: 335, name: '直接訪問'},
                {value: 310, name: '郵件營銷'},
                {value: 234, name: '聯(lián)盟廣告'},
                {value: 135, name: '視頻廣告'},
                {value: 1548, name: '搜索引擎'}
            ]
        }
    ]
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容