mirror of
https://gitee.com/antv/g6.git
synced 2024-11-29 18:28:19 +08:00
feat: add g6 ssr
This commit is contained in:
parent
dbe0868027
commit
7f1dbd9f3c
7
.github/workflows/ci.yml
vendored
7
.github/workflows/ci.yml
vendored
@ -18,6 +18,13 @@ jobs:
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
brew update
|
||||
brew install python3 || : # python doesn't need to be linked
|
||||
brew install pkg-config cairo pango libpng jpeg giflib librsvg
|
||||
pip install setuptools
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
with:
|
||||
|
29
packages/g6-ssr/README.md
Normal file
29
packages/g6-ssr/README.md
Normal file
@ -0,0 +1,29 @@
|
||||
## SSR extension for G6 5.0
|
||||
|
||||
This extension package provides SSR support for G6 5.0, which supports canvas rendering in server side.
|
||||
|
||||
## Usage
|
||||
|
||||
1. Install
|
||||
|
||||
```bash
|
||||
npm install @antv/g6-ssr
|
||||
```
|
||||
|
||||
2. Render
|
||||
|
||||
```js
|
||||
// usage 1
|
||||
import { createGraph } from '@antv/g6-ssr';
|
||||
|
||||
const graph = createGraph({
|
||||
width: 500,
|
||||
height: 500,
|
||||
data: {
|
||||
// your data
|
||||
},
|
||||
// your other options
|
||||
});
|
||||
|
||||
graph.writeToFile('output.png');
|
||||
```
|
BIN
packages/g6-ssr/__tests__/assets/file.pdf
Normal file
BIN
packages/g6-ssr/__tests__/assets/file.pdf
Normal file
Binary file not shown.
132
packages/g6-ssr/__tests__/assets/file.svg
Normal file
132
packages/g6-ssr/__tests__/assets/file.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 118 KiB |
BIN
packages/g6-ssr/__tests__/assets/image.png
Normal file
BIN
packages/g6-ssr/__tests__/assets/image.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 114 KiB |
83
packages/g6-ssr/__tests__/test.spec.ts
Normal file
83
packages/g6-ssr/__tests__/test.spec.ts
Normal file
@ -0,0 +1,83 @@
|
||||
import { existsSync, readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import type { Graph } from '../src';
|
||||
import { createGraph } from '../src';
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace jest {
|
||||
interface Matchers<R> {
|
||||
toMatchFile(path: string): R;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe('createGraph', () => {
|
||||
const fn = async (outputType?: any) => {
|
||||
const data = (await fetch('https://assets.antv.antgroup.com/g6/circular.json').then((res) => res.json())) as any;
|
||||
|
||||
return await createGraph({
|
||||
width: 500,
|
||||
height: 500,
|
||||
outputType,
|
||||
autoFit: 'view',
|
||||
background: 'rgba(100, 80, 180, 0.4)',
|
||||
data,
|
||||
node: {
|
||||
style: {
|
||||
labelText: (d) => d.id,
|
||||
labelFill: '#fff',
|
||||
labelPlacement: 'center',
|
||||
},
|
||||
},
|
||||
layout: {
|
||||
type: 'circular',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
expect.extend({
|
||||
toMatchFile: (received: Graph, path: string) => {
|
||||
const pass = existsSync(path) ? received.toBuffer().equals(readFileSync(path)) : true;
|
||||
if (pass) {
|
||||
return {
|
||||
message: () => 'passed',
|
||||
pass: true,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
message: () => 'expected files are equal',
|
||||
pass: false,
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
it('image image', async () => {
|
||||
const graph = await fn();
|
||||
|
||||
expect(graph).toMatchFile('./assets/image.png');
|
||||
|
||||
graph.writeToFile(join(__dirname, './assets/image'));
|
||||
|
||||
graph.destroy();
|
||||
});
|
||||
|
||||
it('file pdf', async () => {
|
||||
const graph = await fn('pdf');
|
||||
|
||||
graph.writeToFile(join(__dirname, '/assets/file'));
|
||||
|
||||
graph.destroy();
|
||||
});
|
||||
|
||||
it('file svg', async () => {
|
||||
const graph = await fn('svg');
|
||||
|
||||
expect(graph).toMatchFile('./assets/file.svg');
|
||||
|
||||
graph.writeToFile(join(__dirname, './assets/file'));
|
||||
|
||||
graph.destroy();
|
||||
});
|
||||
});
|
11
packages/g6-ssr/jest.config.js
Normal file
11
packages/g6-ssr/jest.config.js
Normal file
@ -0,0 +1,11 @@
|
||||
module.exports = {
|
||||
transform: {
|
||||
'^.+\\.[tj]s$': ['@swc/jest'],
|
||||
},
|
||||
collectCoverageFrom: ['src/**/*.ts'],
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'json'],
|
||||
transformIgnorePatterns: [`<rootDir>/node_modules/.pnpm/(?!(d3-*))`],
|
||||
moduleNameMapper: {
|
||||
'@antv/g6': '<rootDir>/../g6/src',
|
||||
},
|
||||
};
|
36
packages/g6-ssr/package.json
Normal file
36
packages/g6-ssr/package.json
Normal file
@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "g6-ssr",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"keywords": [
|
||||
"antv",
|
||||
"g6",
|
||||
"ssr"
|
||||
],
|
||||
"license": "MIT",
|
||||
"author": "Aarebecca",
|
||||
"main": "lib/index.js",
|
||||
"module": "esm/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "run-p build:*",
|
||||
"build:cjs": "rimraf ./lib && tsc --module commonjs --outDir lib -p tsconfig.build.json",
|
||||
"build:esm": "rimraf ./esm && tsc --module ESNext --outDir esm -p tsconfig.build.json",
|
||||
"ci": "run-s lint type-check build test",
|
||||
"dev": "tsx ./src/index.ts",
|
||||
"lint": "eslint ./src __tests__ --quiet && prettier ./src __tests__ --check",
|
||||
"prepublishOnly": "npm run ci",
|
||||
"test": "jest",
|
||||
"type-check": "tsc --noEmit -p tsconfig.test.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@antv/g": "^6.1.3",
|
||||
"@antv/g-canvas": "^2.0.12",
|
||||
"@antv/g6": "workspace:*",
|
||||
"canvas": "^2.11.2"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"registry": "https://registry.npmjs.org/"
|
||||
}
|
||||
}
|
38
packages/g6-ssr/src/canvas.ts
Normal file
38
packages/g6-ssr/src/canvas.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { Renderer } from '@antv/g-canvas';
|
||||
import { Canvas as G6Canvas } from '@antv/g6';
|
||||
import type { Canvas as NodeCanvas } from 'canvas';
|
||||
import { createCanvas as createNodeCanvas } from 'canvas';
|
||||
import type { Options } from './types';
|
||||
|
||||
/**
|
||||
* <zh/> 创建画布
|
||||
*
|
||||
* <en/> create canvas
|
||||
* @param options <zh/> options 画布配置 | <en/> options canvas configuration
|
||||
* @returns <zh/> [G6 画布, NodeCanvas 画布] | <en/> [G6Canvas, NodeCanvas]
|
||||
*/
|
||||
export function createCanvas(options: Options): [G6Canvas, NodeCanvas] {
|
||||
const { width, height, background, outputType } = options;
|
||||
const nodeCanvas = createNodeCanvas(width, height, outputType as any);
|
||||
const offscreenNodeCanvas = createNodeCanvas(1, 1);
|
||||
|
||||
const g6Canvas = new G6Canvas({
|
||||
width,
|
||||
height,
|
||||
background,
|
||||
// @ts-expect-error missing types
|
||||
canvas: nodeCanvas as any,
|
||||
offscreenCanvas: offscreenNodeCanvas as any,
|
||||
enableMultiLayer: false,
|
||||
renderer: () => {
|
||||
const renderer = new Renderer();
|
||||
const htmlRendererPlugin = renderer.getPlugin('html-renderer');
|
||||
const domInteractionPlugin = renderer.getPlugin('dom-interaction');
|
||||
renderer.unregisterPlugin(htmlRendererPlugin);
|
||||
renderer.unregisterPlugin(domInteractionPlugin);
|
||||
return renderer;
|
||||
},
|
||||
});
|
||||
|
||||
return [g6Canvas, nodeCanvas];
|
||||
}
|
54
packages/g6-ssr/src/graph.ts
Normal file
54
packages/g6-ssr/src/graph.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import { Graph as G6Graph } from '@antv/g6';
|
||||
import { existsSync, lstatSync, writeFileSync } from 'fs';
|
||||
import { createCanvas } from './canvas';
|
||||
import type { Graph, Options } from './types';
|
||||
|
||||
/**
|
||||
* <zh/> 获取输出文件的扩展名
|
||||
*
|
||||
* <en/> Get the extension name of the output file
|
||||
* @param options - <zh/>配置项 | <en/>options
|
||||
* @returns - <zh/>输出文件的扩展名 | <en/>The extension name of the output file
|
||||
*/
|
||||
function getExtendNameOf(options: Options) {
|
||||
const { outputType } = options;
|
||||
if (outputType === 'pdf') return '.pdf';
|
||||
if (outputType === 'svg') return '.svg';
|
||||
return '.png';
|
||||
}
|
||||
|
||||
/**
|
||||
* <zh/> 创建图并等待渲染完成
|
||||
*
|
||||
* <en/> Create a graph and wait for the rendering to complete
|
||||
* @param options - <zh/>图配置项 | <en/>Graph options
|
||||
* @returns - <zh/>扩展图实例 | <en/>Extended graph instance
|
||||
*/
|
||||
export async function createGraph(options: Options) {
|
||||
const [g6Canvas, nodeCanvas] = createCanvas(options);
|
||||
|
||||
const { outputType, ...restOptions } = options;
|
||||
const graph = new G6Graph({
|
||||
...restOptions,
|
||||
container: g6Canvas,
|
||||
});
|
||||
|
||||
// @ts-expect-error extend Graph
|
||||
graph.writeToFile = (file: string) => {
|
||||
const extendName = getExtendNameOf(options);
|
||||
if (!file.endsWith(extendName)) {
|
||||
if (!existsSync(file)) file += extendName;
|
||||
else if (lstatSync(file).isDirectory()) file = `${file}/image${extendName}`;
|
||||
else file += extendName;
|
||||
}
|
||||
|
||||
writeFileSync(file, nodeCanvas.toBuffer());
|
||||
};
|
||||
|
||||
// @ts-expect-error extend Graph
|
||||
graph.toBuffer = () => nodeCanvas.toBuffer();
|
||||
|
||||
await graph.render();
|
||||
|
||||
return graph as Graph;
|
||||
}
|
3
packages/g6-ssr/src/index.ts
Normal file
3
packages/g6-ssr/src/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export { createCanvas } from './canvas';
|
||||
export { createGraph } from './graph';
|
||||
export type { Graph, Options } from './types';
|
19
packages/g6-ssr/src/types.ts
Normal file
19
packages/g6-ssr/src/types.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import type { GraphOptions } from '@antv/g6';
|
||||
import { Graph as G6Graph } from '@antv/g6';
|
||||
|
||||
export interface Options extends Omit<GraphOptions, 'renderer' | 'container'> {
|
||||
width: number;
|
||||
height: number;
|
||||
/**
|
||||
* <zh/> 输出文件类型,默认导出为图片
|
||||
*
|
||||
* <en/> output file type, default export as image
|
||||
* @defaultValue 'image'
|
||||
*/
|
||||
outputType?: 'image' | 'pdf' | 'svg';
|
||||
}
|
||||
|
||||
export interface Graph extends G6Graph {
|
||||
writeToFile: (file: string) => void;
|
||||
toBuffer: () => Buffer;
|
||||
}
|
7
packages/g6-ssr/tsconfig.build.json
Normal file
7
packages/g6-ssr/tsconfig.build.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"paths": {}
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"extends": "./tsconfig.json"
|
||||
}
|
13
packages/g6-ssr/tsconfig.json
Normal file
13
packages/g6-ssr/tsconfig.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"outDir": "lib",
|
||||
"target": "ESNext",
|
||||
"lib": ["ESNext"],
|
||||
"paths": {
|
||||
"@antv/g6": ["../g6/src/index.ts"]
|
||||
}
|
||||
},
|
||||
"extends": "../../tsconfig.json",
|
||||
"include": ["src/**/*", "__tests__/**/*"]
|
||||
}
|
7
packages/g6-ssr/tsconfig.test.json
Normal file
7
packages/g6-ssr/tsconfig.test.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"paths": {}
|
||||
},
|
||||
"include": ["src/**/*", "__tests__/**/*"],
|
||||
"extends": "./tsconfig.json"
|
||||
}
|
Loading…
Reference in New Issue
Block a user