Phantomjs后端生成Echarts图片并导出word

文章介绍了如何在Java后端结合EasyWord库和PhantomJs工具,动态地将Echarts生成的统计图转换为图片,并与文档一起导出。主要步骤包括下载PhantomJs和Echarts相关文件,处理参数,然后使用PhantomJs渲染Echarts图表为图片,最后将图片插入到Word文档中。示例代码展示了如何创建雷达图并将其整合进Word文档的流程。
摘要由CSDN通过智能技术生成

接着我上一篇文章去写,Java EasyWord导出word文档。上一篇文章中图片是固定的(图片路径固定),实际中可能需要根据数据做一些统计图出来,并和文档一起导出(只要我们知道生成的图片路径就可以动态的导出了)。就是相当于,把前端Echarts生成的图片直接在后端生成。

准备工作

  1. 下载并解压PhantomJs,这是官网地址 https://phantomjs.org/download.html,有两个包,Windows和Linux。我展示使用的版本是phantomjs-2.1.1。
  2. jquery-xxx.min.js,任意版本都可以,我使用的是jquery-3.6.0.min.js
  3. echarts.min.js。下载地址:https://echarts.baidu.com/download.html
  4. echarts-convert.js
    (function () {
        var system = require('system');
        var fs = require('fs');
        var config = {
            // define the location of js files
            JQUERY: 'jquery-3.6.0.min.js',
            ECHARTS: 'echarts.min.js',
            // default container width and height
            DEFAULT_WIDTH: '600',
            DEFAULT_HEIGHT: '700'
        }, parseParams, render, pick, usage;
    
        // 提示:命令格式
        usage = function () {
            console.log("\n" + "Usage: phantomjs echarts-convert.js -infile URL -width width -height height" + "\n");
        };
    
        // 选择是否存在设置长宽,否使用默认长宽
        pick = function () {
            var args = arguments, i, arg, length = args.length;
            for (i = 0; i < length; i += 1) {
                arg = args[i];
                if (arg !== undefined && arg !== null && arg !== 'null' && arg != '0') {
                    return arg;
                }
            }
        };
    
        // 处理参数
        parseParams = function () {
            var map = {}, i, key;
            if (system.args.length < 2) {
                usage();
                phantom.exit();
            }
            for (i = 0; i < system.args.length; i += 1) {
                if (system.args[i].charAt(0) === '-') {
                    key = system.args[i].substr(1, i.length);
                    if (key === 'infile') {
                        // get string from file
                        // force translate the key from infile to options.
                        key = 'options';
                        try {
                            map[key] = fs.read(system.args[i + 1]).replace(/^\s+/, '');
                        } catch (e) {
                            console.log('Error: cannot find file, ' + system.args[i + 1]);
                            phantom.exit();
                        }
                    } else {
                        map[key] = system.args[i + 1].replace(/^\s+/, '');
                    }
                }
            }
            return map;
        };
    
        render = function (params) {
            var page = require('webpage').create(), createChart;
    
            page.onConsoleMessage = function (msg) {
                console.log(msg);
            };
    
            page.onAlert = function (msg) {
                console.log(msg);
            };
    
            createChart = function (inputOption, width, height) {
                var counter = 0;
                function decrementImgCounter() {
                    counter -= 1;
                    if (counter < 1) {
                        console.log("The images load error");
                    }
                }
    
                function loadScript(varStr, codeStr) {
                    var script = $('<script>').attr('type', 'text/javascript');
                    script.html('var ' + varStr + ' = ' + codeStr);
                    document.getElementsByTagName("head")[0].appendChild(script[0]);
                    if (window[varStr] !== undefined) {
                        console.log('Echarts.' + varStr + ' has been parsed');
                    }
                }
    
                function loadImages() {
                    var images = $('image'), i, img;
                    if (images.length > 0) {
                        counter = images.length;
                        for (i = 0; i < images.length; i += 1) {
                            img = new Image();
                            img.onload = img.onerror = decrementImgCounter;
                            img.src = images[i].getAttribute('href');
                        }
                    } else {
                        console.log('The images have been loaded');
                    }
                }
                // load opitons
                if (inputOption != 'undefined') {
                    // parse the options
                    loadScript('options', inputOption);
                    // disable the animation
                    options.animation = false;
                }
    
                // we render the image, so we need set background to white.
                $(document.body).css('backgroundColor', 'white');
                var container = $("<div>").appendTo(document.body);
                container.attr('id', 'container');
                container.css({
                    width: width,
                    height: height
                });
                // render the chart
                var myChart = echarts.init(container[0]);
                myChart.setOption(options);
                // load images
                loadImages();
                return myChart.getDataURL();
            };
    
            // parse the params
            page.open("about:blank", function (status) {
                // inject the dependency js
                page.injectJs(config.JQUERY);
                page.injectJs(config.ECHARTS);
    
    
                var width = pick(params.width, config.DEFAULT_WIDTH);
                var height = pick(params.height, config.DEFAULT_HEIGHT);
    
                // create the chart
                var base64 = page.evaluate(createChart, params.options, width, height);
                console.log(base64);
                // define the clip-rectangle
                console.log('\nbase64 complete');
                // exit
                phantom.exit();
            });
        };
    
    // get the args
        var params = parseParams();
    
    // validate the params
        if (params.options === undefined || params.options.length === 0) {
            console.log("ERROR: No options or infile found.");
            usage();
            phantom.exit();
        }
    
    // render the image
        render(params);
    }());
    

 后端测试代码

这里我以生成基础雷达图为例,图表数据找的是Echarts官网例子。

import lombok.Data;

import java.io.Serializable;

/**
 * @author qing
 */
@Data
public class TestPhantomjs implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * echarts 数据生成文件路径
     */
    private String optionPath;
    /**
     * 生成图片所在的路径
     */
    private String picturePath;
}
import lombok.Data;
import net.sf.json.JSONObject;

import java.util.List;

/**
 * 从官网中弄到的示例
 * option = {
 *   title: {
 *     text: 'Basic Radar Chart'
 *   },
 *   legend: {
 *     data: ['Allocated Budget', 'Actual Spending']
 *   },
 *   radar: {
 *     // shape: 'circle',
 *     indicator: [
 *       { name: 'Sales', max: 6500 },
 *       { name: 'Administration', max: 16000 },
 *       { name: 'Information Technology', max: 30000 },
 *       { name: 'Customer Support', max: 38000 },
 *       { name: 'Development', max: 52000 },
 *       { name: 'Marketing', max: 25000 }
 *     ]
 *   },
 *   series: [
 *     {
 *       name: 'Budget vs spending',
 *       type: 'radar',
 *       data: [
 *         {
 *           value: [4200, 3000, 20000, 35000, 50000, 18000],
 *           name: 'Allocated Budget'
 *         },
 *         {
 *           value: [5000, 14000, 28000, 26000, 42000, 21000],
 *           name: 'Actual Spending'
 *         }
 *       ]
 *     }
 *   ]
 * }
 *
 * 雷达图属性
 * @author qing
 */
@Data
public class TestRadar {
    /**
     * 雷达图的标题(描述)
     */
    private String titleText;
    /**
     * 按钮
     */
    private List<String> legendData;
    /**
     * 雷达图形状,默认是有棱角 polygon
     */
    private String radarShape;
    /**
     * 雷达图指标
     */
    private List<JSONObject> radarIndicator;

    private String seriesName;
    /**
     * 基础数据
     */
    private List<JSONObject> seriesData;
}
import com.sushengren.easyword.EasyWord;
import com.sushengren.easyword.annotation.WordProperty;
import com.sushengren.easyword.converters.PictureConverter;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

@Data
@Builder
@AllArgsConstructor
public class TestWord {
    /**
     * 标题
     */
    @WordProperty("title")
    private String title;
    /**
     * 水果列表
     */
    @WordProperty("水果列表")
    private List<FruitTable> fruitTables;
    /**
     * 商品列表
     */
    @WordProperty("商品列表")
    private List<CommodityTable> commodityTables;
    /**
     * 图片
     */
    @WordProperty(value = "logo", converter = PictureConverter.class)
    private InputStream logo;

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class FruitTable {
        /**
         * 名称
         */
        @WordProperty("名称")
        private String name;
        /**
         * 来源
         */
        @WordProperty("来源")
        private String source;
        /**
         * 单价
         */
        @WordProperty("单价")
        private Double price;
        /**
         * 描述
         */
        @WordProperty("描述")
        private String desc;
    }

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class CommodityTable {
        /**
         * 商品类别
         */
        @WordProperty("商品类别")
        private String type;
        /**
         * 商品说明
         */
        @WordProperty("商品说明")
        private String desc;
        /**
         * 类别列表
         */
        @WordProperty("类别列表")
        private List<CategoryTable> categoryTable;
    }

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class CategoryTable {
        /**
         * 名称
         */
        @WordProperty("名称")
        private String name;
        /**
         * 来源
         */
        @WordProperty("来源")
        private String source;
        /**
         * 单价
         */
        @WordProperty("单价")
        private Double price;
        /**
         * 描述
         */
        @WordProperty("描述")
        private String desc;
    }

    public static void main(String[] args) throws Exception {
        // 这是表格的数据 start
        List<FruitTable> fruitTables = new ArrayList<>();
        FruitTable f1 = new FruitTable("苹果", "烟台", 5.0, "又红又大的苹果。");
        fruitTables.add(f1);
        FruitTable f2 = new FruitTable("芒果", "海南", 8.0, "好吃不贵的芒果。");
        fruitTables.add(f2);
        FruitTable f3 = new FruitTable("柑橘", "宜昌", 2.5, "好一个柑橘。");
        fruitTables.add(f3);

        List<CategoryTable> categoryTables1 = new ArrayList<>();
        CategoryTable a1 = new CategoryTable("芭蕉", "火焰山", 6.5, "烈日炎炎,芭蕉冉冉。");
        categoryTables1.add(a1);
        CategoryTable a2 = new CategoryTable("樱桃", "泰安", 25.0, "红了樱桃,绿了芭蕉。");
        categoryTables1.add(a2);
        List<CommodityTable> commodityTables = new ArrayList<>();
        CommodityTable c1 = new CommodityTable("水果", "这是一批水果", categoryTables1);
        commodityTables.add(c1);

        List<CategoryTable> categoryTables2 = new ArrayList<>();
        CategoryTable a3 = new CategoryTable("空调", "小行星", 2800.0, "全靠空调续命。");
        categoryTables2.add(a3);
        CategoryTable a4 = new CategoryTable("热水器", "哈哈哈", 1525.0, "零冷水。");
        categoryTables2.add(a4);
        CommodityTable c2 = new CommodityTable("家电", "这是一堆家电", categoryTables2);
        commodityTables.add(c2);
        // 这是表格的数据 end
        // 先组装雷达图数据
        TestRadar testRadar = radarDataAssembly();
        // 生成图片,并返回图片路径
        TestPhantomjs radarPicturePath = getRadarPicturePath(testRadar);
        TestWord testWord = TestWord.builder()
                .title("这是一个word导出测试")
                .fruitTables(fruitTables)
                .commodityTables(commodityTables)
                .logo(new FileInputStream(radarPicturePath.getPicturePath()))
                .build();
        File file = new File("D:\\Temp\\title.docx");
        FileOutputStream out = new FileOutputStream("D:\\Temp\\测试.docx");
        EasyWord.of(file).doWrite(testWord).toOutputStream(out);

    }

    /**
     * 雷达图数据拼接
     * @return
     * @throws Exception
     */
    private static TestRadar radarDataAssembly() throws Exception {
        TestRadar radar = new TestRadar();
        radar.setTitleText("''");

        List<net.sf.json.JSONObject> radarIndicators = new LinkedList<>();
        net.sf.json.JSONObject j1 = new net.sf.json.JSONObject();
        j1.put("name", "Sales");
        j1.put("max", 6500);
        radarIndicators.add(j1);
        net.sf.json.JSONObject j2 = new net.sf.json.JSONObject();
        j2.put("name", "Administration");
        j2.put("max", 16000);
        radarIndicators.add(j2);
        net.sf.json.JSONObject j3 = new net.sf.json.JSONObject();
        j3.put("name", "Information Technology");
        j3.put("max", 30000);
        radarIndicators.add(j3);
        net.sf.json.JSONObject j4 = new net.sf.json.JSONObject();
        j4.put("name", "Customer Support");
        j4.put("max", 38000);
        radarIndicators.add(j4);
        net.sf.json.JSONObject j5 = new net.sf.json.JSONObject();
        j5.put("name", "Development");
        j5.put("max", 52000);
        radarIndicators.add(j5);
        net.sf.json.JSONObject j6 = new net.sf.json.JSONObject();
        j6.put("name", "Marketing");
        j6.put("max", 25000);
        radarIndicators.add(j6);

        radar.setRadarIndicator(radarIndicators);
        radar.setSeriesName("'Budget vs spending'");
        List<String> legendData = new LinkedList<>();
        legendData.add("'Allocated Budget'");
        legendData.add("'Actual Spending'");
        radar.setLegendData(legendData);
        // 最主要的数据 series 数据的拼接
        List<net.sf.json.JSONObject> seriesData = new LinkedList<>();
        net.sf.json.JSONObject json1 = new net.sf.json.JSONObject();
        json1.put("name", "'Allocated Budget'");
        List<Integer> list1 = new LinkedList<>();
        list1.add(4200);list1.add(3000);list1.add(20000);list1.add(35000);list1.add(50000);list1.add(18000);
        json1.put("value", list1);
        seriesData.add(json1);
        net.sf.json.JSONObject json2 = new net.sf.json.JSONObject();
        json2.put("name", "'Actual Spending'");
        List<Integer> list2 = new LinkedList<>();
        list2.add(5000);list2.add(14000);list2.add(28000);list2.add(26000);list2.add(42000);list2.add(21000);
        json2.put("value", list2);
        seriesData.add(json2);

        radar.setRadarShape("'polygon'");
        radar.setSeriesData(seriesData);
        return radar;
    }

    /**
     * 生成图片,并返回图片路径
     * @param radar
     * @return
     * @throws Exception
     */
    private static TestPhantomjs getRadarPicturePath(TestRadar radar) throws Exception {
        TestPhantomjsUtil testPhantomjsUtil = new TestPhantomjsUtil();
        return testPhantomjsUtil.getRadarPicturePath(radar);
    }
}
import org.springframework.stereotype.Component;
import sun.misc.BASE64Decoder;

import java.io.*;
import java.util.UUID;

/**
 * 测试Echarts后台渲染生成图片
 * @author qing
 */
@Component
public class TestPhantomjsUtil {

    /**
     * phantomjs 路径
     */
    private static final String PHANTOMJS_PATH = "D:/phantomjs-2.1.1-windows/bin/phantomjs.exe";
    private static final String UPLOAD_FILE_PATH = "D:/Temp/echartjs";
    /**
     * echarts获取图片base64编码URL头
     */
    private static final String BASE64FORMAT = "data:image/png;base64,";
    /**
     * 生成图片(默认图片宽高),返回图片路径
     * @param option echarts 图表
     * @throws Exception
     * @return
     */
    public TestPhantomjs getPicturePath(String option) throws Exception {
        // 得到option 的 js文件路径
        String optionPath = writeFile(option);
        // 得到图片处理好后的base64码
        String pictureBase64 = getPictureBase64(optionPath);
        // 生成图片,并返回图片路径
        String picturePath = getPicturePathFromBase64(pictureBase64);
        TestPhantomjs phantomjs = new TestPhantomjs();
        phantomjs.setOptionPath(optionPath);
        phantomjs.setPicturePath(picturePath);
        return phantomjs;
    }

    /**
     * 命令格式:
     * phantomjs echarts-convert.js -infile optionURl -width width -height height
     * 可选参数:-width width -height height
     * 备注:
     * phantomjs添加到环境变量中后可以直接使用,这里防止环境变量配置问题所以直接使用绝对路径
     */
    public String getPictureBase64(String dataPath) throws Exception {
        String base64 = "";
        String echartsPath = UPLOAD_FILE_PATH + File.separator + "echarts-convert.js";
        String cmd = PHANTOMJS_PATH + " " + echartsPath + " -infile " + dataPath
//                + " -width " + pictureWidth + " -height " + pictureHeight;
        ;
        System.out.println("PhantomjsUtil.getPictureBase64() 执行的命令是:"+ cmd);
        Process process = Runtime.getRuntime().exec(cmd);
        try (
                // 将快照图片生成字节数组
                BufferedReader input = new BufferedReader(new InputStreamReader(process.getInputStream()))
             )
        {
            String line;
            while ((line = input.readLine()) != null) {
                if (line.startsWith(BASE64FORMAT)) {
                    base64 = line;
                    break;
                }
            }
            return base64;
        }
    }

    /**
     * options生成文件存储
     * @param options
     * @return
     */
    public String writeFile(String options) throws Exception {
        String dataPath = UPLOAD_FILE_PATH + File.separator + "file" +File.separator+ UUID.randomUUID().toString().substring(0, 8) +".js";
        /* option写入文本文件 用于执行命令*/
        try (BufferedWriter out = new BufferedWriter(new FileWriter(new File(dataPath))))
        {
            out.write(options);
            // 把缓存区内容压入文件
            out.flush();
        }
        return dataPath;
    }

    public String getPicturePathFromBase64(String base) throws Exception {
        String base64Pic = base;
        // 图像数据为空
        if (base64Pic == null) {
            throw new Exception("图片base64码不存在。");
        }
        String picturePath = UPLOAD_FILE_PATH + File.separator + "file" +File.separator + System.currentTimeMillis()+".png";
        // 如果要返回file文件这边return就可以了,存到临时文件中
        try(OutputStream out = new FileOutputStream(picturePath))
        {
            BASE64Decoder decoder = new BASE64Decoder();
            //前台在用Ajax传base64值的时候会把base64中的+换成空格,所以需要替换回来。
            String baseValue = base64Pic.replaceAll(" ", "+");
            //去除base64中无用的部分
            byte[] b = decoder.decodeBuffer(baseValue.replace(BASE64FORMAT, ""));
            for (int i = 0; i < b.length; ++i) {
                // 调整异常数据
                if (b[i] < 0) {
                    b[i] += 256;
                }
            }
            out.write(b);
            out.flush();
        }
        return picturePath;
    }

    /**
     * 基本雷达图
     * @param radar
     * @throws Exception
     */
    public TestPhantomjs getRadarPicturePath(TestRadar radar) throws Exception {
        String options = "option = {\n" +
                "  title: {\n" +
                "    text: "+radar.getTitleText()+"\n" +
                "  },\n" +
                "  legend: {\n" +
                "    data: "+radar.getLegendData()+"\n" +
                "  },\n" +
                "  radar: {\n" +
                "    shape: "+radar.getRadarShape()+",\n" +
                "    indicator: "+radar.getRadarIndicator()+"\n" +
                "  },\n" +
                "  series: [\n" +
                "    {\n" +
                "      name: "+radar.getSeriesName()+",\n" +
                "      type: 'radar',\n" +
                "      data: "+radar.getSeriesData()+"\n" +
                "    }\n" +
                "  ]\n" +
                "}";
        TestPhantomjs phantomjs = new TestPhantomjsUtil().getPicturePath(options);
        return phantomjs;
    }
}

在本地生成的js以及图片

在我们本地D:\Temp\echartjs\file文件夹中会生成两个文件,这是示例。

导出word最终展示效果

 

 

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
图表图片的步骤是什么? 回答: 生成echarts图表图片的步骤如下: 1. 首先,需要安装部署Phantomjs,可以将下载好的Phantomjs放在任意位置。 2. 找到echarts-convert.js文件,并将其放在与Phantomjs相同的目录下。 3. 在命令行中执行命令,例如在Linux系统中可以执行nohup phantomjs echarts-convert.js -s -p 50130 > echarts.log 2>&1 &,而在Windows系统中可以执行C:\Users\Administrator\Desktop\phantomjs-2.1.1-windows\bin>phantomjs C:\Users\Administrator\Desktop\echartsconvert\echarts-convert.js -s -p 9090。\[2\]\[3\] 4. 调用接口生成图片,可以通过发送请求来调用接口生成echarts图表的图片。 #### 引用[.reference_title] - *1* *2* [java后台生成echarts图表图片](https://blog.csdn.net/zixuanyankai/article/details/130702369)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [java 实现后台生成echarts 图片](https://blog.csdn.net/weixin_43831289/article/details/119645323)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值