从 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 变得更好,让数据可视化社区活跃起来!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/180595.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Django RestFramework

安装restframework pip install djangorestframework pip install markdown # Markdown support for the browsable API. pip install django-filter # Filtering support安装其他模块 pip install pillowpip install django-cors-headers建模和迁移数据 drf包含四个…

Linux常用命令——basename命令

在线Linux命令查询工具 basename 打印目录或者文件的基本名称 补充说明 basename命令用于打印目录或者文件的基本名称。basename和dirname命令通常用于shell脚本中的命令替换来指定和指定的输入文件名称有所差异的输出文件名称。 语法 basename(选项)(参数)选项 --help&…

深度学习可解释性Python库

本文整理了10个常用于可解释AI的Python库&#xff0c;方便我们更好的理解AI模型的决策。 原文阅读 什么是XAI&#xff1f; XAI&#xff08;Explainable AI&#xff09;的目标是为模型的行为和决策提供合理的解释&#xff0c;这有助于增加信任、提供问责制和模型决策的透明度…

SAP_ABAP_编程基础_二进制文件_SMW0中上传与下载

SAP ABAP 顾问&#xff08;开发工程师&#xff09;能力模型_Terry谈企业数字化的博客-CSDN博客文章浏览阅读448次。目标&#xff1a;基于对SAP abap 顾问能力模型的梳理&#xff0c;给一年左右经验的abaper 快速成长为三年经验提供超级燃料&#xff01;https://blog.csdn.net/j…

(2)(2.1) Lightware SF40/C(360度)

文章目录 前言 1 安装SF40c 2 连接自动驾驶仪 3 通过地面站进行配置 4 参数说明 前言 Lightware SF40/C 360 度激光雷达(Lightware SF40/C 360degree lidar)可在 Copter-3.4 及更高版本的 Loiter 模式下用于物体回避。 &#xff01;Warning 该功能尚未在各种情况下进行过…

STM32F407-14.3.5-01捕获_比较通道

捕获/比较通道 每一个捕获/比较通道都是围绕着一个捕获/比较寄存器(包含影子寄存器) 包括: 捕获的输入部分(数字滤波、多路复用和预分频器)&#xff0c; 输出部分(比较器和输出控制)。 中文参考手册中框图分成了三大模块, 把框图合并成了一个整体,以便更好的理解捕获输…

分布式机器学习、联邦学习、多智能体的区别和联系——一文进行详细解释

1 分布式机器学习、联邦学习、多智能体介绍 最近这三个方面的论文都读过&#xff0c;这里写一篇博客归纳一下&#xff0c;以方便搞这几个领域的其他童鞋入门。我们先来介绍以下这三种机器学习范式的基本概念。 1.1 分布式机器学习介绍 分布式机器学习(distributed machine l…

除自身以外数组的乘积——力扣算法

题目 给你一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法&#xff0c;且在 O(n) 时…

再探Java集合系列—LinkedList

单向链表 双向链表 LinkedList适用于什么场景&#xff1f; 适用于需要频繁插入和删除元素的场景&#xff0c;例如消息聊天系统&#xff0c;一开始并不明确有多少记录&#xff0c;可以在空间满足的情况下不断增加数据 LinkedList的特点有哪些&#xff1f; LinkedList的底层采…

中兴小鲜50 ZTE 畅行50 刷机救砖演示机7543n root 虎贲 展锐 T760 解锁BL

系统信息 网络制式 支持中国移动、中国电信、中国联通、中国广电四大运营商5G频段&#xff1b;支持4G/3G/2G 系统平台 MyOS 13.0&#xff08;基于Android 13&#xff09; 硬件信息 处理器 展锐T760&#xff0c;高性能8核5G芯片 存储 6GB RAM128GB ROM 扩展 不支持 电池容…

【算法萌新闯力扣】:旋转链表

力扣题目&#xff1a;旋转链表 开篇 今天是备战蓝桥杯的第25天和算法村开营第3天&#xff01;经过这3天的学习&#xff0c;感觉自己对链表的掌握程度大大地提升&#xff0c;尤其是在帮村里的同学讨论相关问题时。本篇文章&#xff0c;给大家带来一道旋转链表的题目&#xff0c…

vue+echarts实现依赖关系无向网络拓扑结图节点折叠展开策略

目录 引言 一、设计 1. 树状图&#xff08;不方便呈现节点之间的关系&#xff0c;次要考虑&#xff09; 2. 力引导依赖关系图 二、力引导关系图 三、如何实现节点的Open Or Fold 1. 设计逻辑 节点展开细节 节点收缩细节 代码实现 四、结果呈现 五、完整代码 引言 我…

额,收到阿里云给的赔偿了!

众所周知&#xff0c;就在刚过去不久的11月12号&#xff0c;阿里云突发了一次大规模故障&#xff0c;影响甚广。 以至于连咱们这里评论区小伙伴学校的洗衣机都崩了&#xff08;手动doge&#xff09;。 这么关键的双11节点&#xff0c;这么多热门业务和产品&#xff0c;这么大规…

Linux如何查找某个路径下大于1G的文件

find 命令可以用于在 Linux 或 macOS 系统中查找文件和目录。如果你想查找大于1GB的文件&#xff0c;可以使用 -size 选项结合 参数。以下是一个示例&#xff1a; find /path/to/search -type f -size 1G这里的 /path/to/search 是你要搜索的目录的路径。这个命令将查找该目录…

Unity3d 灯光阴影开启,法线贴图出现BUG

URP项目打开灯光的阴影后&#xff0c;法线贴图出现BUG 解决方案&#xff1a;按照下图所示调整材质的选项即可

XML Schema中的attributeFormDefault

XML Schema中的attributeFormDefault属性&#xff0c;用以指定元素的属性默认是否必须带有命名空间前缀。 attributeFormDefault属性可以取值qualified或unqualified&#xff0c;默认值是unqualified。 当取值为qualified时&#xff0c;表示属性必须用命名空间作为前缀&#x…

精力管理金字塔

精力管理金字塔 由协和医学院的张遇升博士在《掌控精力&#xff1a;不疲惫的身心管理术》一书中提出&#xff0c;分层次对精力管理提出了解析和有效的建议。 模型介绍 精力管理是一个可以学会的技能&#xff0c;学会了科学的精力管理方法&#xff0c;就能使自己的精力越来越好…

股票技术从初级到高级,从实盘进阶到摩尔缠论

一、教程描述 摩尔缠论是什么&#xff1f;一个伟大的缠论分支体系&#xff0c;由顶尖高手创立的缠论分支&#xff0c;这个顶尖高手&#xff0c;江湖上的代号为摩尔&#xff0c;可能是一个人&#xff0c;或者是一群人。摩尔缠论&#xff0c;基于缠论的核心思想与基础理论&#…

面试必须要知道的MySQL知识--索引

10 索引 10.1 数据页存储结构 10.1.1 数据页的各个部分 在讲索引之前&#xff0c;让我们看看一个单独的数据页是什么样子的 去除掉一些我们不太需要那么关注的部分后&#xff0c;简化如下&#xff1a; 也就是说平时我们在一个表里插入的一行一行的数据会存储在数据页里&#…

Leetcode算法系列| 3. 无重复字符的最长子串

目录 1.题目2.题解C# 解法一&#xff1a;滑动窗口算法C# 解法二&#xff1a;索引寻找Java 解法一&#xff1a;滑动窗口算法Java 解法二&#xff1a;遍历字符串 1.题目 给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长子串 的长度。 示例1: 输入: s "ab…