相关文章推荐


HTML 页面渲染好的 HighCharts 图表,可以获取其 SVG 信息并发送后台,进一步创建图表文件(JPG、PNG等)

试图不经前端渲染直接后台生成图表文件,只靠 HighCharts 目前是无法实现的

如果可以在后台模拟前端 HTML 的渲染过程,是否就可以解决问题了呢?答案是肯定的。问题关键在于 How ,怎么做。

PhantomJS (幻影)就是用以实现模拟前端渲染的独立程序,下载 地址 ,这在 HighCharts 官网也是被支持的,相关 链接

java后端生成excel java后端生成echarts图表_json

这是摘自 HighCharts 官方一段说明,相关 链接

研究官方的说明文档固然是好的,但是未免枯燥无聊且操作复杂, 能不能傻瓜式一键搞定?

java后端生成excel java后端生成echarts图表_java_02

Espen Hovlandsdal 已经帮我们封装好了!

highcharts-png-renderer ,访问 地址 ,从Git上把项目 clone 下来后,结构如下图所示

java后端生成excel java后端生成echarts图表_json_03

将从 PhantomJS 官网下载的 phantomjs.exe 文件放到 highcharts-png-renderer 子文件路径下

并 执行命令: phantomjs run.js ,如图所示

java后端生成excel java后端生成echarts图表_json_04

命令窗口输出 Listening on port 11942

打开 PostMan 模拟HTTP请求,参数和返回值如下, 惊不惊喜!意不意外!

类型

POST

java后端生成excel java后端生成echarts图表_java后端生成excel_05

URL

http://127.0.0.1:11942/

参数

java后端生成excel java后端生成echarts图表_json_06

中篇:行百里半九十,下面才是正题

如果你可以将 highcharts-png-renderer 做成服务,随机自启、持续运行、时刻待命,这是最好的解决方案!!!

关于 将bat做成服务 的相关知识,参考 地址

但是,如果想智能化处理渲染器的关停,那么就要自己实现了

1、 Demo 的项目结构

依据业务逻辑划分,将 highcharts-png-renderer 重命名为 renderer ,将 renderer phantomjs.exe mould.json 置于文件夹 highcharts-renderer 内,其磁盘路径在 RenderUtil.java 中有使用到,这应写入配置文件中

项目结构如下图所示:

java后端生成excel java后端生成echarts图表_json_07

2、 mould.json

HighCharts的Option属性包含很多参数,大多数参数对于一个稳定的项目来说是固定不变的,为了减少代码冗余,建一个模板Option,使用时读取,只将需要修改的少量参数替换掉即可

mould.json模板示例:

{
  "global": {
    "useUTC": false
  "chart": {
    "renderTo": "container",
    "type": "spline",
    "height": 300,
    "width": 500,
    "marginTop": 45,
    "marginBottom": 45
  "title": {
    "text": "",
    "style": {
      "color": "rgb(139, 134, 134)",
      "font": "bold 1.1em 'Trebuchet MS', Verdana, sans-serif"
  "credits": {
    "enabled": false
  "legend": {
    "enabled": false
  "xAxis": {
    "title": {
      "enabled": true,
      "text": "",
      "align": "high",
      "style": {
        "color": "rgb(114, 111, 111)"
    "labels": {
      "style": {
        "color": "rgb(114, 111, 111)"
    "dateTimeLabelFormats": {
      "day": "%e. %b",
      "minute": "%H:%M"
    "type": "datetime",
    "showLastLabel": true,
    "minRange": 60000,
    "tickPixelInterval": 80,
    "lineWidth": 1,
    "lineColor": "#A0A0A0",
    "gridLineWidth": 0,
    "gridLineColor": "#E8E8E8"
  "yAxis": {
    "title": {
      "enabled": true,
      "text": "",
      "style": {
        "color": "rgb(114, 111, 111)"
    "labels": {
      "style": {
        "color": "rgb(114, 111, 111)"
    "minRange": 0.0004,
    "tickPixelInterval": 25,
    "lineWidth": 1,
    "lineColor": "#A0A0A0",
    "gridLineWidth": 0,
    "gridLineColor": "#E8E8E8"
  "plotOptions": {
    "spline": {
      "lineWidth": 1,
      "pointInterval": 60000,
      "marker": {
        "enabled": false
  "series": [
      "id": "curve_line",
      "color": "rgba(0, 0, 255, 1.0)",
      "data": [
        [1538830800000, 9.020376],
        [1538830801000, 9.020376],
        [1538834400000, 10.574599],
        [1538838000000, 6.3690405],
        [1538841600000, 4.102905]
}

3、 RenderUtil.java

渲染器工具类,包含 mould.json 载入、 highcharts-png-renderer

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import java.io.File;
import java.io.IOException;
import java.util.Date;
public class RenderUtil {
    private static String highChartOptionMouldString = null;
    private static Process highChartsRendererProcess = null;
    //以下常量可写入配置文件
    private static final String localTempFolder = "D:/temp/charts";
    private static final String highChartsRendererPath = "D:/highcharts-renderer";
    private static final String highChartsRendererUrl = "http://127.0.0.1:11942/";
     * 格式化文件路径
     * @param path
     * @return
    public static String formatPath(String path) {
        if (StringUtils.isBlank(path)) {
            return "";
        while (path.indexOf("\\") > -1) {
            path = path.replace("\\", "/");
        while (path.indexOf("//") > -1) {
            path = path.replace("//", "/");
        return path;
     * 载入HighCharts的模板Option
    private static void loadHighChartOptionMould() throws IOException {
        String mouldPath = highChartsRendererPath + "/mould.json";
        mouldPath = formatPath(mouldPath);
        String content = FileUtils.readFileToString(new File(mouldPath), "UTF-8");
        if (null != content) {
            JSONObject mouldJson = JSONObject.parseObject(content);//为了验证格式的正确性
            highChartOptionMouldString = mouldJson.toJSONString();
     * 启动 HighCharts 渲染器
     * @return
    synchronized public static boolean startRenderer() {
        if (null != highChartsRendererProcess) {
            endRenderer();
        try {
            String phantomJs = highChartsRendererPath + "/phantomjs";
            phantomJs = formatPath(phantomJs);
            String runJs = highChartsRendererPath + "/renderer/run.js";
            runJs = formatPath(runJs);
            Runtime rt = Runtime.getRuntime();
            highChartsRendererProcess = rt.exec(phantomJs + " " + runJs);
            (new Robot()).delay(10 * 1000);//延时10s,防止服务尚未启动完全即刻发送HTTP请求
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
     * 销毁 HighCharts 渲染器
     * @return
    synchronized public static void endRenderer() {
        if (null != highChartsRendererProcess) {
            highChartsRendererProcess.destroy();
            highChartsRendererProcess = null;
            try {
                (new Robot()).delay(10 * 1000);//延时10s,防止服务尚未完全关闭即刻再启服务
            } catch (Exception e) {
                e.printStackTrace();
     * 发送给HighCharts渲染器,取得图表的字节流
     * @param param
     * @return
    synchronized private static byte[] post2Renderer(String param) {
        CloseableHttpResponse response = null;
        try {
            HttpPost post = new HttpPost(highChartsRendererUrl);
            if (StringUtils.isNotBlank(param)) {
                StringEntity entity = new StringEntity(param, "utf-8");
                entity.setContentEncoding("UTF-8");
                entity.setContentType("application/json");
                post.setEntity(entity);
            // TODO 处理请求超时
            CloseableHttpClient client = HttpClients.createDefault();
            response = client.execute(post);
            HttpEntity entity = response.getEntity();
            byte[] bytes = EntityUtils.toByteArray(entity);
            EntityUtils.consume(entity);//关闭流
            return bytes;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            if (null != response) {
                try {
                    response.close();
                } catch (Exception e) {
                    e.printStackTrace();
     * 存储图表到本地,返回文件路径
     * @param chartTitle 图表标题
     * @param xAxisTitle x轴的标题
     * @param yAxisTitle y轴的标题
     * @param data       数据
     * @return
    synchronized public static String storeChart(String chartTitle, String xAxisTitle, String yAxisTitle, JSONArray data) {
        if (null == highChartOptionMouldString) {
            try {
                loadHighChartOptionMould();
            } catch (IOException e) {
                e.printStackTrace();
                return null;
        //变相实现深度拷贝
        JSONObject mouldJson = JSONObject.parseObject(highChartOptionMouldString);
        JSONObject title = mouldJson.getJSONObject("title");
        title.put("text", chartTitle);
        JSONObject xAxis = mouldJson.getJSONObject("xAxis");
        JSONObject xTitle = xAxis.getJSONObject("title");
        xTitle.put("text", xAxisTitle);
        JSONObject yAxis = mouldJson.getJSONObject("yAxis");
        JSONObject yTitle = yAxis.getJSONObject("title");
        yTitle.put("text", yAxisTitle);
        mouldJson.put("series", data);
        if (null != data.get(0)) {
            JSONObject line1 = data.getJSONObject(0);
            JSONArray data1 = line1.getJSONArray("data");
            if (null != data1 && data1.size() < 2) {
                JSONObject plotOptions = mouldJson.getJSONObject("plotOptions");
                JSONObject spline = plotOptions.getJSONObject("spline");
                JSONObject marker = spline.getJSONObject("marker");
                marker.put("enabled", true);//显示散点
        byte[] bytes = post2Renderer(mouldJson.toJSONString());
        if (null == bytes) {
            return null;
        try {
            String localPath = localTempFolder + "/" + (new Date()).getTime() + ".png";
            localPath = formatPath(localPath);
            FileUtils.writeByteArrayToFile(new File(localPath), bytes);
            return localPath;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
}

4、 RunMain.java

这是一个简单的测试用例主函数,如下

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import java.util.ArrayList;
import java.util.List;
public class RunMain {
    public static void main(String[] args) {
        d0Render();
     * 启动服务生成图表文件到本地
    synchronized public static void d0Render() {
        try {
            boolean success = RenderUtil.startRenderer();
            if (success) {
                String chartPath = loadData2Chart();
                if (null != chartPath) {
                    System.out.println("数据载入成功,图表文件生成后的路径:" + chartPath);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            RenderUtil.endRenderer();
     * 载入数据生成图表,并存储本地
     * @return
    private static String loadData2Chart() {
        double[] l1p1 = {1538830800000.0, 9.020376};//线1点1
        double[] l1p2 = {1538830801000.0, 9.020376};
        double[] l1p3 = {1538834400000.0, 10.574599};
        double[] l1p4 = {1538838000000.0, 6.3690405};
        double[] l1p5 = {1538841600000.0, 4.102905};
        double[] l2p1 = {1538830800000.0, 5.020376};//线2点1
        double[] l2p2 = {1538834400000.0, 5.574599};
        double[] l2p3 = {1538841600000.0, 5.102905};
        List<double[]> data1 = new ArrayList<double[]>();
        data1.add(l1p1);
        data1.add(l1p2);
        data1.add(l1p3);
        data1.add(l1p4);
        data1.add(l1p5);
        // TODO 按x值排序
        List<double[]> data2 = new ArrayList<double[]>();
        data2.add(l2p1);
        data2.add(l2p2);
        data2.add(l2p3);
        // TODO 处理时区错乱
        JSONObject line1 = new JSONObject();
        line1.put("id", "blue");
        line1.put("color", "rgba(0, 0, 255, 1.0)");
        line1.put("data", data1);
        JSONObject line2 = new JSONObject();
        line2.put("id", "red");
        line2.put("color", "rgba(255, 0, 0, 1.0)");
        line2.put("data", data2);
        JSONArray lines = new JSONArray();
        lines.add(line1);
        lines.add(line2);
        return RenderUtil.storeChart("演示图表", "Date", "Value", lines);
}

5、实测效果图

控制台输出: 数据载入成功,图表文件生成后的路径:D:/temp/charts/*.png

java后端生成excel java后端生成echarts图表_java_08

下篇:还是有坑在等你

1、渲染器服务的端口占用

(1)人工配置服务端口

文件夹 highcharts-png-renderer 内的 config.json 可配置服务端口

(2)程序实现灵活检查

//TODO

2、 HighCharts 时区错乱问题

蓝线 峰值点对应x坐标值为 14:00 ,但是我们的输入值 1538834400000.0 毫秒是 22:00 ,正好差8个小时!!

double[] l1p3 = {1538834400000.0, 10.574599};

错误原因是渲染器使用了国际时间,东八区的我们自然会比国际时间早8个小时

解决方案:在 mould.json 中配置参数

"global": {
    "useUTC": false
  }

测试结果:渲染器中 毫无卵用 ,经HTML前端渲染后却是有效的,至于原因嘛。。简单推断可能是 highcharts-png-renderer 的服务所采用的 HighCharts 版本太低

再次尝试解决:使用最新 HighCharts 包替换 highcharts-png-renderer 服务内 libs 文件夹下的 highcharts.js highcharts-more.js

测试结果:毫无变化,偶尔产生纯黑图表

赶时间的我即不想研究源码也不想瞎猜,简单暴力点:

RunMain.java 的方法 loadData2Chart // TODO 处理时区错乱 处直接再加8小时

//手动处理时区错乱问题
for (double[] data : data1) {
    data[0] += 8 * 60 * 60 * 1000;
for (double[] data : data2) {
    data[0] += 8 * 60 * 60 * 1000;
}

@All

测试结果:符合预期


java后端生成excel java后端生成echarts图表_java_09

3、渲染器的稳定性问题

测试中发现渲染器经常卡死无响应,严重阻塞执行流程

优化方案:调整类 RenderUtil.java 中的方法 post2Renderer ,增加超时判断并停服重连

/**
     * 发送给HighCharts渲染器,取得图表的字节流
    synchronized private static byte[] post2Renderer(String param) {
        CloseableHttpResponse response = null;
        try {
            HttpPost post = new HttpPost(highChartsRendererUrl);
            if (StringUtils.isNotBlank(param)) {
                StringEntity entity = new StringEntity(param, "utf-8");
                entity.setContentEncoding("UTF-8");
                entity.setContentType("application/json");
                post.setEntity(entity);
            RequestConfig config = RequestConfig.custom()
                    .setSocketTimeout(2 * 60 * 1000).setConnectTimeout(30 * 1000).build();
            post.setConfig(config);
            CloseableHttpClient client = HttpClients.createDefault();
            for (int i = 0; i < 3; i++) {
                try {
                    response = client.execute(post);
                    HttpEntity entity = response.getEntity();
                    byte[] bytes = EntityUtils.toByteArray(entity);
                    EntityUtils.consume(entity);//关闭流
                    return bytes;
                } catch (SocketTimeoutException e) {
                    e.printStackTrace();
                    endRenderer();
                    startRenderer();
            System.out.println("Try and try but fail: " + param);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (null != response) {
                try {
                    response.close();
                } catch (Exception e) {
                    e.printStackTrace();
        return null;
    }

4、 Data 数据应当是已排序的

RunMain.java 的方法 loadData2Chart 中的变量 data1 data2 put 进 line 之前,应当是按x值已排序好的,升序或降序都可以,否则图表会出现错乱,如下图所示

double[] l1p1 = {1538830800000.0, 9.020376};//线1点1
    double[] l1p2 = {1538830801000.0, 9.020376};
    double[] l1p3 = {1538834400000.0, 10.574599};
    double[] l1p4 = {1538838000000.0, 6.3690405};
    double[] l1p5 = {1538841600000.0, 4.102905};
    List<double[]> data1 = new ArrayList<double[]>();
    data1.add(l1p1);//顺序不对
    data1.add(l1p5);
    data1.add(l1p4);
    data1.add(l1p3);
    data1.add(l1p2);

java后端生成excel java后端生成echarts图表_java后端生成excel_10

解决方案:

Collections.sort(data1, new Comparator<double[]>() {
    public int compare(double[] p1, double[] p2) {
        if (p1[0] < p2[0]) {
            return -1;
        if (p1[0] > p2[0]) {
            return 1;
        return 0;
});

附:

pom.xml

<dependencies>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.8.1</version>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.54</version>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.6</version>
        </dependency>
    </dependencies>

高斯模糊 android 高斯模糊怎么用

模糊的实现有很多方法,例如均值模糊和中值模糊。均值模糊同意使用了卷积操作,它使用的卷积核中的各个元素都相等,且相加等于1.也就是说,卷积后得到的像素值时期邻域内各个像素值的平均值。而中值模糊则是选择邻域内对所有像素排序后的中值替换到原颜色。一个更高级的模糊方法是高斯模糊。C#代码:using System.Collections; using

linux BIOS查看当前的系统盘 linux查看bios版本号

这可以通过 6 种方法来实现,下面我来演示一下怎么做。方法一:使用 dmidecode 命令dmidecode 是一个读取电脑 DMI(桌面管理接口Desktop Management Interface)表内容并且以人类可读的格式显示系统硬件信息的工具。(也有人说是读取 SMBIOS —— 系统管理 BIOSSystem Management BIOS )这个表包含系统硬件组件的说明,也包含如序

 
推荐文章