echartsconvert图表生成

314 阅读3分钟

数据统计图表生成方案调研

一、背景

项目需要生成并推送数据统计图表,主要为折线图等常见类型的数据可视化图形。

二、技术选型

起初考虑使用jfeechart实现,但是参考案例效果图,直接放弃;

第二种方式是使用python的matplotlib包实现,这种方式需要在java代码中调用python程序来实现,且生成的图片并不美观;

最后,参考gitee上的案例,选择echartsconvert来实现,该项目选择echarts数据可视化图标库来生成图片,比较美观。且是独立部署的项目,减少与Java服务的耦合。

三、部署实践

1、安装phantomjs

官网下载phantomjs.org/download.ht…

下载解压后,放到指定目录,并配置环境变量;

2、运行echartsconvert服务

项目下载:gitee.com/saintlee/ec…

原项目已经很久不维护,可将echarts.min.js替换为最新版本

使用phantomjs运行EChartConvert,并指定端口

phantomjs echarts-convert.js -s -p 8090

3、使用模板生成echarts图表

  1. 创建FreeMarker模板文件

option.ftl

{
	title: {
		text: '周阅读人数'
	},
	tooltip: {
		trigger: 'axis'
	},
	legend: {
		data: ['男', '女']
	},
	xAxis: [{
		type: 'category',
		boundaryGap: false,
		data: $ {
			dateArray
		},
		axisLabel: {
			interval: 0,
			rotate: 30,
		}
	}],
	yAxis: [{
		type: 'value'
	}],
	series: [{
			name: '男',
			type: 'line',
			data: $ {
				dataArray1
			}
		},
		{
			'name': '女',
			'type': 'line',
			data: $ {
				dataArray2
			}
		}
	]
}

注意:

使用freemarker模板引擎,方便动态传输数据;

上述模板为echarts折线图堆叠,使用时可以在echarts官网选择合适的类型并在线调试,然后复制修改为ftl文件,将需要动态添加的数据设为freemarker插值;

  1. 构建请求参数,调用接口生成图片
public class EchartsUtil {
    private static String url = "http://localhost:8090";
    private static final String SUCCESS_CODE = "1";

    public static String generateEchartsBase64(String option) {
        String base64 = "";
        if (option == null) {
            return base64;
        }
        option = option.replaceAll("\s+", "").replaceAll(""", "'");

        // 将option字符串作为参数发送给echartsConvert服务器
        Map<String, Object> params = new HashMap<>();
        params.put("opt", option);
        //调用echartsconvert接口,生成图片,返回base64编码
        String response = HttpUtil.post(url, params);

        // 解析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;
    }
}
  1. 封装FreemarkerUtil工具类,用于合并数据模型和ftl模板
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 cfg = new Configuration(Configuration.VERSION_2_3_22);
        // 设置默认编码
        cfg.setDefaultEncoding("UTF-8");
        // 设置模板所在文件夹
        cfg.setDirectoryForTemplateLoading(new File(path + templateDirectory));

        cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
        // 获取模板
        Template template = cfg.getTemplate(templateFileName);

        // 将数据模型与模板合并
        try (StringWriter stringWriter = new StringWriter()) {
            template.process(datas, stringWriter);
            stringWriter.flush();
            return stringWriter.getBuffer().toString();
        }
    }
}
  1. 构建指标数据,调用工具方法,获取base64编码,并生成图片
public static void main(String[] args) throws TemplateException, IOException {
    // 变量
    String[] dateArray = {"20221128", "20221205", "20221212", "20221219", "20221226", "20230102", "20230109"};
    Object[] dataArray1 = {300, 400, 500, 672, 736, 900, 743};
    Object[] dataArray2 = {400, 465, 788, 298, 353, 520, 673};

    // 模板参数
    HashMap<String, Object> datas = new HashMap<>();
    datas.put("dateArray", JSON.toJSONString(dateArray));
    datas.put("dataArray1", JSON.toJSONString(dataArray1));
    datas.put("dataArray2", JSON.toJSONString(dataArray2));

    // 生成option字符串
    String option = FreemarkerUtil.generateString("option.ftl", "template/echarts", datas);

    System.out.println(option);

    // 根据option参数
    String base64 = EchartsUtil.generateEchartsBase64(option);

    System.out.println("BASE64:" + base64);
    generateImage(base64, "/data/image/test.png");
}
    
    //根据返回的base64编码生成图片
    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();
        }
    }

最终生成趋势图如下:

test.png