从 0 到 1 开发一个 node 命令行工具
G2 5.0 推出了服务端渲染的能力,为了让开发者更快捷得使用这部分能力,最写了一个 node 命令行工具 g2-ssr-node:用于把 G2 的 spec 转换成 png、jpeg 或者 pdf 等。基本的使用如下:
$ g2-ssr-node g2png -i ./bar.json -o ./bar.png
其中 bar.json是一个如下绘制条形图的 G2 spec:
{"type": "interval","data": [{ "genre": "Sports", "sold": 275 },{ "genre": "Strategy", "sold": 115 },{ "genre": "Action", "sold": 120 },{ "genre": "Shooter", "sold": 350 },{ "genre": "Other", "sold": 150 }],"encode": {"x": "genre","y": "sold"}
}
最后得到如下的图片 bar.png:

那接下来就来看看如何从 0 到 1 实现 g2-ssr-node。
初始化环境
首先新建一个文件夹并且用 npm 初始化项目:
$ mkdir g2-ssr-node && cd g2-ssr-node && npm init -y
在得到的 package.json 中增加一个 bin 字段,该字段指向了包里面的可执行文件,也就是最后运行 $ g2-ssr-node 时候执行的文件。
{"bin": "bin/g2-ssr-node.js",
}
接下来新增 bin 目录、新增 g2-ssr-node.js,并且输入以下的内容:
#!/usr/bin/env nodeconsole.log('hello world!')
其中第一行说明用 node 来执行 g2-ssr-node.js 文件,类似于当运行 $ g2-ssr-node的时候调用 $ node g2-ssr-node.js。
这之后在项目根目录下运行 npm link 会把当前包安装到全局。如果打开控制台输入 g2-ssr-node能打印出 hello world!, 那么说明开发环境已经搭建好了,可以进一步写代码了。
安装 commander
首先安装 commnder 工具包来简化开发命令行的成本,比如帮助解析参数、展现使用错误和实现提示信息等。
npm i commander -D
本文实现的 g2-ssr-node 有一个子命令:g2png,将 G2 的 spec 转换成图片。该子命令有两个参数:
- -i, --input <filename>: 指定包含需要转换的 spec 的文件地址
- -o, --output <filename>: 指定输出图表的文件地址
修改 bin/g2-ssr-node.js 如下用于满足上述需求:
#!/usr/bin/env node
const { Command } = require("commander");
const process = require("node:process");
const { version } = require("../package");
const { g2png } = require("./g2png.js");const program = new Command();program.name("g2-ssr-node").description("CLI for ssr of @antv/g2").version(version);program.command("g2png") // 添加子命令.description("Convert a G2 spec to an PNG image") // 添加对子命令的描述.option("-i, --input <filename>", "filename for the input spec") // 声明参数.option("-o, --output <filename>", "filename for the output image") // 声明参数.action((options) => g2png(options).then(() => process.exit())); // 真正的执行函数program.parse(); // 解析
然后新增 bin/g2png.js这个文件,并且导出g2png(options)这个函数,用于根据指定的配置渲染图片。
// bin/g2png.jsfunction g2png(options) {console.log(options)
}module.exports = { g2png };
如果一切顺利的话,运行如下测试命令:
$ g2-ssr-node g2png -i ./bar.json -o ./bar.png
会在控制台输出:{ input: './bar.json', output: './bar.png' }。
当然也可以运行 g2-ssr-node g2png --help看看控制台输出的帮助信息是否符合预期:
Usage: g2-ssr-node g2png [options]Convert a G2 spec to an PNG imageOptions:-i, --input <filename> filename for the input spec-o, --output <filename> filename for the output image-h, --help display help for command
如果没有问题的话,就可以进入下一步,实现 g2png 函数。
实现 g2png 函数
g2png 主要包含三个步骤:
- 根据 input 地址读取 JSON 内容,解析成 JavaScript 对象,得到需要渲染的 spec。
- 将得到的 spec 通过 renderImage 函数渲染成 node canvas 中的 canvas 对象。
- 将 canvas 对象转换成 png 流并且写入 output 地址。
首先我们安装 node canvas。 node canvas 是一个实现了 canvas 标准的 node 库,主要用于做 canvas 的服务端渲染,更多的 API 参考其文档。
$ npm i canvas -D
修改 bin/g2png.js代码如下:
const fs = require("fs");
const { renderImage } = require("./renderImage.js");function readJSONSync(input) {const data = fs.readFileSync(input, "utf-8");return JSON.parse(data);
}async function g2png({ input, output }) {console.log(`Start converting ${input} to ${output} ...`);// 读取并且转化 Specconst spec = await readJSONSync(input);// 将 Spec 渲染成 node canvas 中的 canvasconst canvas = await renderImage(spec);// 将 canvas 转化成 png 流并且写入 output 地址const out = fs.createWriteStream(output);const stream = canvas.createPNGStream();stream.pipe(out);return new Promise((resolve, reject) => {out.on("finish", () => {console.log(`Convert ${input} to ${output} successfully.`);resolve();}).on("error", () => reject());});
}module.exports = { g2png };
那么接下来我们来看看 renderImage 函数的实现。
实现 renderImage 函数
首先新建 bin/renderImage.js文件,并且输入如下的代码。这段代码将一个 node-canvas 创建的 canvas 对象传给了 G,用于创建一个画布,然后 G2 将 spec 渲染到这个画布上,并且将 canvas 返回。
// bin/renderImage.js
const { createCanvas } = require("canvas");
const { stdlib, render: renderChart } = require("../dist/g2.js");
const { Canvas } = require("../dist/g");
const { Renderer } = require("../dist/g-canvas");async function renderImage(options) {// 创建 canvasconst { width = 640, height = 480, ...rest } = options;const [gCanvas, canvas] = createGCanvas(width, height);// 根据 spec 和 context 渲染图表到 canvas 上const spec = { ...rest, width, height };const context = {canvas: gCanvas,library: stdlib,createCanvas: () => createCanvas(300, 150),};await new Promise((resolve) => renderChart(spec, context, resolve));return canvas;
}function createGCanvas(width, height, type) {const canvas = createCanvas(width, height, type);const offscreenCanvas = createCanvas(1, 1);const renderer = new Renderer();// 移除一些和 DOM 相关的交互const htmlRendererPlugin = renderer.getPlugin("html-renderer");const domInteractionPlugin = renderer.getPlugin("dom-interaction");renderer.unregisterPlugin(htmlRendererPlugin);renderer.unregisterPlugin(domInteractionPlugin);return [new Canvas({width,height,canvas,renderer,offscreenCanvas,devicePixelRatio: 2, // 解决高分辨率屏不清晰的问题}),canvas,];
}module.exports = { renderImage };
这里大家可能注意到了在上面代码中并没有直接从 G2 或者 G 中导入我们需要的函数,那么接下来就来看看为什么。
// renderImage.js
// 从 dist 导出
const { stdlib, render: renderChart } = require("../dist/g2.js");
const { Canvas } = require("../dist/g");
const { Renderer } = require("../dist/g-canvas");
打包 commonjs
JavaScript 常见的模块系统有两种 ESM 和 commonjs,而低版本的 Node 只支持 commonjs。虽然 G2 和 G 都提供了 commonjs 的版本,但是它们的依赖却不一定,比如 D3.js 只提供了 ESM 版本。为了让 g2-ssr-node 能在低版本的 Node 中运行,需要把 G2 和 G 以及它们的依赖都打包成 commonjs。
首先分别创建以下三个文件,并且输入以下的内容:
// @antv/g2.js
export * from '@antv/g2';
// @antv/g.js
export * from '@antv/g';
// @antv/g-canvas.js
export * from '@antv/g-canvas'
然后使用 Rollup 和它的一系列插件来将上述的文件打包成 commonjs 模块:
npm i rollup @rollup/plugin-commonjs @rollup/plugin-json @rollup/plugin-node-resolve @rollup/plugin-terser -D
配置文件如下:
// rollup.config.js
const resolve = require("@rollup/plugin-node-resolve");
const cjs = require("@rollup/plugin-commonjs");
const json = require("@rollup/plugin-json");
const terser = require("@rollup/plugin-terser");const common = {plugins: [resolve(), cjs(), json(), terser()],external: ["@antv/g"],
};module.exports = [{input: "antv/g2.js",output: {file: "dist/g2.js",format: "cjs", // 打包成 commonjs},...common,},{input: "antv/g.js",output: {file: "dist/g2.js",format: "cjs", // 打包成 commonjs},...common,},{input: "antv/g-canvas.js",output: {file: "dist/g2-canvas.js",format: "cjs", // 打包成 commonjs},...common,},
];
然后在控制台输入 npx rollup -c就会发现已经多了一个 dist 文件夹,这之后就可以验证是是否成功了。
验证是否成功
第一步,在项目根目录下创建一个 bar.json文件,并且输入以下内容:
{"type": "interval","data": [{ "genre": "Sports", "sold": 275 },{ "genre": "Strategy", "sold": 115 },{ "genre": "Action", "sold": 120 },{ "genre": "Shooter", "sold": 350 },{ "genre": "Other", "sold": 150 }],"encode": { "x": "genre", "y": "sold" },"viewStyle": { "plotFill": "white" }
}
接下来运行:
$ g2-ssr-node g2png -i ./bar.json -o ./bar.png
如果没有问题的话,就会在项目的根目录出现一张名叫 bar.png的图片。

小结
到这里简单版本的 g2-ssr-node 就已经开发完成了,发包的过程就不在这里赘述了。最后大体的代码结构如下:
- antv- g2.js- g.js- g-canvas.js
- dist- g2.js- g.js- g-canvas.js
- bin- g2-ssr-node.js- g2png.js- renderImage.js
- rollup.config.js
- bar.json
- package.json
除了更多的能力之外(比如将 G2 spec 转换成 jpeg 和 pdf,以及通过 API 的形式调用),还需要考虑 Test、Lint、CI 和文档一些相关的问题。完整的代码、能力和文档可以在 g2-ssr-node 查看。当然感兴趣的小伙伴也可以提 PR,去实现将 G2 spec 转换成 SVG 命令,参考 SVG Output。
最后,g2-ssr-node 算是 G2 5.0 生态中的新成员,而目前 G2 5.0 在收集相关的一些生态,当然也可以在这里提出一些想法,组团来实现。期望大家一起,让 G2 变得更好,让数据可视化社区活跃起来!
相关文章:
从 0 到 1 开发一个 node 命令行工具
G2 5.0 推出了服务端渲染的能力,为了让开发者更快捷得使用这部分能力,最写了一个 node 命令行工具 g2-ssr-node:用于把 G2 的 spec 转换成 png、jpeg 或者 pdf 等。基本的使用如下: $ g2-ssr-node g2png -i ./bar.json -o ./bar.…...
VsCode中使用功能vite创建vue3+js项目报错
VsCode中使用功能vite创建vue3js项目报错 VsCode中使用功能vite创建vue3js项目import模块报错如下处理方法 VsCode中使用功能vite创建vue3js项目import模块报错如下 处理方法 在项目根目录新建jsconfig.json {"compilerOptions": {"baseUrl": "./&q…...
COGVLM论文解读(COGVLM:VISUAL EXPERT FOR LARGE LANGUAGE MODELS)
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、摘要二、引言三、模型方法1、模型思路2、融合公式 四、训练方法总结 前言 2023年5月18日清华&智谱AI发布并开源VisualGLM-6B以来,清华KEG&…...
Flink-时间流与水印
时间流与水印 一、背景二、时间语义1.事件时间(event time)2.读取时间(ingestion time)3.处理时间(processing time) 三、水印-Watermarks1.延迟和正确性2.延迟事件3.顺序流4.无序流5.并行流 四、Windows1.…...
BiLSTM-CRF的中文命名实体识别
项目地址:NLP-Application-and-Practice/11_BiLSTM-ner-bilstm-crf/11.3-BiLSTM-CRF的中文命名实体识别/ner_bilstm_crf at master zz-zik/NLP-Application-and-Practice (github.com) 读取renmindata.pkl文件 read_file_pkl.py # encoding:utf-8import pickle# …...
paddle detection 训练参数
#####################################基础配置##################################### # 检测算法使用YOLOv3,backbone使用MobileNet_v1,数据集使用roadsign_voc的配置文件模板,本配置文件默认使用单卡,单卡的batch_size=1 # 检测模型的名称 architecture: YOLOv3 # 根据…...
用bat制作图片马——一句话木马
效果图 代码 ECHO OFF TITLE PtoR MODE con COLS55 LINES25 color 0A:main cls echo.当前时间:%date% %time% echo.欢迎使用图片马制作工具 echo.请确保图片和php在同一路径下 echo.echo 请将图像文件拖放到此窗口并按 Enter: set /p "imagefile&q…...
json_encode() 返回 false
当 json_encode() 返回 false 时,表示 JSON 编码过程失败。这通常是因为要编码的数据包含了无效的 UTF-8 字符,而默认情况下 json_encode() 会对无效的 UTF-8 字符进行严格的处理 通过添加 JSON_INVALID_UTF8_IGNORE 选项,你告诉 json_encod…...
Android-Jetpack--Hilt详解
善学者尽其理,善行者究其难 一,定义 Hilt是针对dagger2的二次封装依赖注入框架,至于什么是依赖注入,在Android开源框架--Dagger2详解-CSDN博客 中已经讲解,建议大家先去了解Dagger2之后,再来看Hilt。这样就…...
Docker 下载加速
文章目录 方式1:使用 网易数帆容器镜像仓库进行下载。方式2:配置阿里云加速。方式3:方式4:结尾注意 Docker下载加速的原理是,在拉取镜像时使用一个国内的镜像站点,该站点已经缓存了各个版本的官方 Docker 镜…...
1091 Acute Stroke (三维搜索)
题目可能看起来很难的样子,但是看懂了其实挺简单的。(众所周知,pat考察英文水平) 题目意思大概是:给你一个L*M*N的01长方体,求全为1的连通块的总体积大小。(连通块体积大于T才计算在内…...
java elasticsearch 桶聚合(bucket)
Elasticsearch指标聚合,就是类似SQL的统计函数,指标聚合可以单独使用,也可以跟桶聚合一起使用,下面介绍Java Elasticsearch指标聚合的写法。 实例: // 首先创建RestClient,后续章节通过RestClient对象进行…...
【人生苦短,我学 Python】(4)Python 常用内置数据类型 II —— 序列数据类型(str、tuple、list、bytes和bytearray)
目录 简述 / 前言1. str 数据类型(字符串)1.1 str对象1.2 str对象属性和方法1.3 字符串编码1.4 转义字符1.5 字符串的格式化 2. tuple 数据类型(元组)2.1 创建元组对象 3. list 数据类型(列表)3.1 创建列表…...
Android 9.0 系统默认显示电量百分比
Android 9.0 系统默认显示电量百分比 近来收到项目需求需要设备默认显示电量百分比,具体修改参照如下: /frameworks/base/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java private void updateShowPercent() {final boolean showin…...
原神:夏洛蒂是否值得培养?全队瞬抬治疗量不输五星,但缺点也很明显
作为四星冰系治疗角色,夏洛蒂的实战表现可以说相当让人惊喜。不仅有相当有意思的普攻动作以及技能特效,而且她还有治疗和挂冰等功能性。下面就来详细聊聊夏洛蒂是否值得培养。 【治疗量让人惊喜,但也有缺点】 说实话,在使用夏洛蒂…...
Sublime text 添加到鼠标右键菜单,脚本实现
Sublime text 添加到鼠标右键菜单 Windows Registry Editor Version 5.00 [HKEY_CLASSES_ROOT\*\shell\SublimeText] "Open with Sublime Text" "Icon""D:\\Program Files\\Sublime Text\\sublime_text.exe,0" [HKEY_CLASSES_ROOT\*\shell\Subl…...
【算法】离散化 与 哈希 之间的区别
离散化(Discretization)和哈希(Hashing)是两种不同的数据处理技术,用于处理不同类型的问题。 1. 离散化(Discretization): 离散化是将一组连续的数据映射到有限个离散值的过程。主要…...
Android : GPS定位 获取当前位置—简单应用
示例图: MainActivity.java package com.example.mygpsapp;import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat;import android.Manif…...
目标检测——R-CNN算法解读
论文:Rich feature hierarchies for accurate object detection and semantic segmentation 作者:Ross Girshick, Jeff Donahue, Trevor Darrell, Jitendra Malik 链接:https://arxiv.org/abs/1311.2524 代码:http://www.cs.berke…...
基于傅里叶变换的运动模糊图像恢复算法matlab仿真
目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1、傅里叶变换与图像恢复 4.2、基于傅里叶变换的运动模糊图像恢复算法原理 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 %获取角度 img…...
UDP(Echoserver)
网络命令 Ping 命令 检测网络是否连通 使用方法: ping -c 次数 网址ping -c 3 www.baidu.comnetstat 命令 netstat 是一个用来查看网络状态的重要工具. 语法:netstat [选项] 功能:查看网络状态 常用选项: n 拒绝显示别名&#…...
高频面试之3Zookeeper
高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个?3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制(过半机制࿰…...
CocosCreator 之 JavaScript/TypeScript和Java的相互交互
引擎版本: 3.8.1 语言: JavaScript/TypeScript、C、Java 环境:Window 参考:Java原生反射机制 您好,我是鹤九日! 回顾 在上篇文章中:CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...
Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
服务器--宝塔命令
一、宝塔面板安装命令 ⚠️ 必须使用 root 用户 或 sudo 权限执行! sudo su - 1. CentOS 系统: yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh2. Ubuntu / Debian 系统…...
C++.OpenGL (14/64)多光源(Multiple Lights)
多光源(Multiple Lights) 多光源渲染技术概览 #mermaid-svg-3L5e5gGn76TNh7Lq {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-3L5e5gGn76TNh7Lq .error-icon{fill:#552222;}#mermaid-svg-3L5e5gGn76TNh7Lq .erro…...
音视频——I2S 协议详解
I2S 协议详解 I2S (Inter-IC Sound) 协议是一种串行总线协议,专门用于在数字音频设备之间传输数字音频数据。它由飞利浦(Philips)公司开发,以其简单、高效和广泛的兼容性而闻名。 1. 信号线 I2S 协议通常使用三根或四根信号线&a…...
Go 并发编程基础:通道(Channel)的使用
在 Go 中,Channel 是 Goroutine 之间通信的核心机制。它提供了一个线程安全的通信方式,用于在多个 Goroutine 之间传递数据,从而实现高效的并发编程。 本章将介绍 Channel 的基本概念、用法、缓冲、关闭机制以及 select 的使用。 一、Channel…...
深入理解Optional:处理空指针异常
1. 使用Optional处理可能为空的集合 在Java开发中,集合判空是一个常见但容易出错的场景。传统方式虽然可行,但存在一些潜在问题: // 传统判空方式 if (!CollectionUtils.isEmpty(userInfoList)) {for (UserInfo userInfo : userInfoList) {…...
Ubuntu Cursor升级成v1.0
0. 当前版本低 使用当前 Cursor v0.50时 GitHub Copilot Chat 打不开,快捷键也不好用,当看到 Cursor 升级后,还是蛮高兴的 1. 下载 Cursor 下载地址:https://www.cursor.com/cn/downloads 点击下载 Linux (x64) ,…...
