Skip to content

Commit

Permalink
docs: update readme, add ssr document (#5539)
Browse files Browse the repository at this point in the history
* docs: update raadme demo

* docs: add ssr document

* chore: update ssr document

* chore: add link
  • Loading branch information
hustcc authored Sep 13, 2023
1 parent 7618ced commit 20d663c
Show file tree
Hide file tree
Showing 5 changed files with 240 additions and 12 deletions.
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,23 +66,23 @@ const data = [
// Instantiate a new chart.
const chart = new Chart({
container: 'container',
theme: 'classic',
});

// Specify visualization.
chart
.interval() // Create an interval mark and add it to the chart.
.data(data) // Bind data for this mark.
.encode('x', 'genre') // Assign genre column to x position channel.
.encode('y', 'sold'); // Assign sold column to y position channel.
.interval() // Create an interval mark and add it to the chart.
.data(data) // Bind data for this mark.
.encode('x', 'genre') // Assign genre column to x position channel.
.encode('y', 'sold') // Assign sold column to y position channel.
.encode('color', 'genre'); // Assign genre column to color channel.

// Render visualization.
chart.render();
```

If all goes well, you can get the following lovely bar chart!

<img src="https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*K-7URYaij4kAAAAAAAAAAAAADmJ7AQ/original" width="640" height="480" alt="example">
<img src="https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*XqCnTbkpAkQAAAAAAAAAAAAADmJ7AQ/fmt.webp" width="640" alt="example">

## 📮 Contributing

Expand Down
12 changes: 6 additions & 6 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,23 +68,23 @@ const data = [
// 初始化图表实例
const chart = new Chart({
container: 'container',
theme: 'classic',
});

// 声明可视化
chart
.interval() // 创建一个 Interval 标记
.data(data) // 绑定数据
.encode('x', 'genre') // 编码 x 通道
.encode('y', 'sold'); // 编码 y 通道
.interval() // 创建一个 Interval 标记
.data(data) // 绑定数据
.encode('x', 'genre') // 编码 x 通道
.encode('y', 'sold') // 编码 y 通道
.encode('color', 'genre'); // 编码 color 通道

// 渲染可视化
chart.render();
```

如果一切顺利,你可以得到下面的柱状图!

<img src="https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*K-7URYaij4kAAAAAAAAAAAAADmJ7AQ/original" width="640" height="480" alt="example">
<img src="https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*XqCnTbkpAkQAAAAAAAAAAAAADmJ7AQ/fmt.webp" width="640" alt="example">

## 📮 参与贡献

Expand Down
97 changes: 97 additions & 0 deletions __tests__/unit/ssr/index.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { createCanvas } from 'canvas';
import { Canvas } from '@antv/g';
import { Renderer } from '@antv/g-canvas';
import { Plugin as DragAndDropPlugin } from '@antv/g-plugin-dragndrop';
import { Chart } from '../../../src';

function createNodeGCanvas(width: number, height: number): Canvas {
// Create a node-canvas instead of HTMLCanvasElement
const nodeCanvas = createCanvas(width, height);
// A standalone offscreen canvas for text metrics
const offscreenNodeCanvas = createCanvas(1, 1);

// Create a renderer, unregister plugin relative to DOM.
const renderer = new Renderer();
// Remove html plugin to ssr.
const htmlRendererPlugin = renderer.getPlugin('html-renderer');
renderer.unregisterPlugin(htmlRendererPlugin);
const domInteractionPlugin = renderer.getPlugin('dom-interaction');
renderer.unregisterPlugin(domInteractionPlugin);
renderer.registerPlugin(
new DragAndDropPlugin({ dragstartDistanceThreshold: 10 }),
);
return new Canvas({
width,
height,
// @ts-ignore
canvas: nodeCanvas,
renderer,
// @ts-ignore
offscreenCanvas: offscreenNodeCanvas,
});
}

function writePNG(nodeCanvas) {
return new Promise<string>((resolve, reject) => {
const f = path.join(os.tmpdir(), `${Math.random()}.png`);
const out = fs.createWriteStream(f);
const stream = nodeCanvas.createPNGStream();
stream.pipe(out);
out.on('finish', () => resolve(f)).on('error', reject);
});
}

async function renderG2BySSR() {
const width = 600;
const height = 400;

const gCanvas = createNodeGCanvas(width, height);

// A tabular data to be visualized.
const data = [
{ genre: 'Sports', sold: 275 },
{ genre: 'Strategy', sold: 115 },
{ genre: 'Action', sold: 120 },
{ genre: 'Shooter', sold: 350 },
{ genre: 'Other', sold: 150 },
];

// Instantiate a new chart.
const chart = new Chart({
width,
height,
// Set the g canvas with node-canvas.
canvas: gCanvas,
// Set the createCanvas function.
// @ts-ignore
createCanvas: () => {
// The width attribute defaults to 300, and the height attribute defaults to 150.
// @see https://stackoverflow.com/a/12019582
return createCanvas(width, height) as unknown as HTMLCanvasElement;
},
});

// Specify visualization.
chart
.interval() // Create an interval mark and add it to the chart.
.data(data) // Bind data for this mark.
.encode('x', 'genre') // Assign genre column to x position channel.
.encode('y', 'sold') // Assign sold column to y position channel.
.encode('color', 'genre'); // Assign genre column to color channel.

// Render visualization.
await chart.render();

return writePNG(chart.getContext().canvas?.getConfig().canvas);
}

describe('ssr', () => {
it('SSR will return a image', async () => {
const f = await renderG2BySSR();

expect(fs.existsSync(f)).toBe(true);
});
});
6 changes: 6 additions & 0 deletions site/docs/manual/extra-topics/ssr.en.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
title: Server Side Render(SSR)
order: 12
---

<embed src="@/docs/manual/extra-topics/ssr.zh.md"></embed>
125 changes: 125 additions & 0 deletions site/docs/manual/extra-topics/ssr.zh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
---
title: 服务端渲染(SSR)
order: 12
---

服务端渲染(SSR)是指在非浏览器环境渲染出图表,比如在 NodeJs、Python、Java、PHP 等后端语言环境中,一般在后端语言中,最终出来的是一张没有交互和动画的图片。一般使用的场景:

- 后端预渲成图片,提高页面打开的速度
- 脚本批处理,便于传播
- 服务端可视化服务
- 生成图片进行截图对比,用于代码单测
- ...


## 原理

G2 可视化引擎的 SSR 原理很简单。G2 能绘制图表的核心是需要有 `Canvas` 的 API,在浏览器中,有浏览器标准的 Canvas 绘图接口,而在 SSR 中,只需要在对应的语言脚本中提供 Canvas 绘图 API 即可。


## 在 NodeJS 中使用

正常在浏览器中使用 G2 的时候,可以参考文档[开始使用](/manual/introduction/getting-started),而在 NodeJS 中,只需要在创建 Chart 实例的时候,传入对应的构建 `Canvas` 对象的方法即可。整体代码如下:

```ts
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { createCanvas } from 'canvas';
import { Canvas } from '@antv/g';
import { Renderer } from '@antv/g-canvas';
import { Plugin as DragAndDropPlugin } from '@antv/g-plugin-dragndrop';
import { Chart } from '../../../src';

function createNodeGCanvas(width: number, height: number): Canvas {
// Create a node-canvas instead of HTMLCanvasElement
const nodeCanvas = createCanvas(width, height);
// A standalone offscreen canvas for text metrics
const offscreenNodeCanvas = createCanvas(1, 1);

// Create a renderer, unregister plugin relative to DOM.
const renderer = new Renderer();
// Remove html plugin to ssr.
const htmlRendererPlugin = renderer.getPlugin('html-renderer');
renderer.unregisterPlugin(htmlRendererPlugin);
const domInteractionPlugin = renderer.getPlugin('dom-interaction');
renderer.unregisterPlugin(domInteractionPlugin);
renderer.registerPlugin(
new DragAndDropPlugin({ dragstartDistanceThreshold: 10 }),
);
return new Canvas({
width,
height,
// @ts-ignore
canvas: nodeCanvas,
renderer,
// @ts-ignore
offscreenCanvas: offscreenNodeCanvas,
});
}

function writePNG(nodeCanvas) {
return new Promise<string>((resolve, reject) => {
const f = path.join(os.tmpdir(), `${Math.random()}.png`);
const out = fs.createWriteStream(f);
const stream = nodeCanvas.createPNGStream();
stream.pipe(out);
out.on('finish', () => resolve(f)).on('error', reject);
});
}

async function renderG2BySSR() {
const width = 600;
const height = 400;

const gCanvas = createNodeGCanvas(width, height);

// A tabular data to be visualized.
const data = [
{ genre: 'Sports', sold: 275 },
{ genre: 'Strategy', sold: 115 },
{ genre: 'Action', sold: 120 },
{ genre: 'Shooter', sold: 350 },
{ genre: 'Other', sold: 150 },
];

// Instantiate a new chart.
const chart = new Chart({
width,
height,
// Set the g canvas with node-canvas.
canvas: gCanvas,
// Set the createCanvas function.
// @ts-ignore
createCanvas: () => {
// The width attribute defaults to 300, and the height attribute defaults to 150.
// @see https://stackoverflow.com/a/12019582
return createCanvas(width, height) as unknown as HTMLCanvasElement;
},
});

// Specify visualization.
chart
.interval() // Create an interval mark and add it to the chart.
.data(data) // Bind data for this mark.
.encode('x', 'genre') // Assign genre column to x position channel.
.encode('y', 'sold') // Assign sold column to y position channel.
.encode('color', 'genre'); // Assign genre column to color channel.

// Render visualization.
await chart.render();

return writePNG(chart.getContext().canvas?.getConfig().canvas);
}

await renderG2BySSR();
```

也可以在 G2 的单测目录中找到对应的代码 [__tests__/unit/ssr/index.spec.ts](https://github.com/antvis/G2/tree/v5/__tests__/unit/ssr/index.spec.ts)


## 在其他服务端语言环境中使用

因为 G2 的代码是使用 JavaScript 编写和开发,所以无法直接在 Python、Java、PHP 等语言环境中使用,但是可以在服务中安装 NodeJS 环境,然后使用对应的后端语言命令行 API 去驱动上述的 NodeJS 代码去执行 SSR。

参考《[python 调用 node js](https://juejin.cn/s/python%20%E8%B0%83%E7%94%A8%20node%20js)》,其他语言类似。

0 comments on commit 20d663c

Please sign in to comment.