Skip to content

Commit

Permalink
feat: support g6 ssr (#6454)
Browse files Browse the repository at this point in the history
* refactor: canvas support disable mutiple layers

* refactor: fix globalThis access

* feat: add g6 ssr

* chore: config build options

* chore: update readme

---------

Co-authored-by: antv <[email protected]>
  • Loading branch information
Aarebecca and antv authored Oct 31, 2024
1 parent 81eaa54 commit e4ebb8d
Show file tree
Hide file tree
Showing 28 changed files with 800 additions and 42 deletions.
7 changes: 7 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"@commitlint/config-conventional": "^18.6.3",
"@playwright/test": "^1.48.2",
"@rollup/plugin-commonjs": "^25.0.8",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^15.3.0",
"@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-typescript": "^11.1.6",
Expand Down
63 changes: 63 additions & 0 deletions packages/g6-ssr/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
## SSR extension for G6 5.0

This extension package provides SSR support for G6 5.0, which supports canvas rendering in server side.

## Usage

### Install

```bash
npm install @antv/g6-ssr
```

### Render in JavaScript API

> For complete options, please refer to [G6 Graph Options](https://g6.antv.antgroup.com/api/graph/option)
```js
import { createGraph } from '@antv/g6-ssr';

const graph = await createGraph({
width: 500,
height: 500,
data: {
// data
},
// other options
});

graph.exportToFile('image');
// -> image.png
```

### Render in CLI

```bash
npx g6-ssr export -i [graph-options].json -o ./image
```

### Export SVG / PDF

When render in JavaScript API, you can pass `outputType` option to export SVG or PDF.

```js
const graph = await createGraph({
width: 500,
height: 500,
data: {
// data
},
outputType: 'svg', // or 'pdf'
// other options
});
```

When render in CLI, you can pass `-t` or `--type` option to export SVG or PDF.

```bash
npx g6-ssr export -i [graph-options].json -o ./file -t pdf
```

## License

MIT
Binary file added packages/g6-ssr/__tests__/assets/file.pdf
Binary file not shown.
132 changes: 132 additions & 0 deletions packages/g6-ssr/__tests__/assets/file.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added packages/g6-ssr/__tests__/assets/image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
113 changes: 113 additions & 0 deletions packages/g6-ssr/__tests__/graph-options.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
{
"width": 500,
"height": 500,
"autoFit": "view",
"background": "rgba(100, 80, 180, 0.4)",
"data": {
"nodes": [
{ "id": "0" },
{ "id": "1" },
{ "id": "2" },
{ "id": "3" },
{ "id": "4" },
{ "id": "5" },
{ "id": "6" },
{ "id": "7" },
{ "id": "8" },
{ "id": "9" },
{ "id": "10" },
{ "id": "11" },
{ "id": "12" },
{ "id": "13" },
{ "id": "14" },
{ "id": "15" },
{ "id": "16" },
{ "id": "17" },
{ "id": "18" },
{ "id": "19" },
{ "id": "20" },
{ "id": "21" },
{ "id": "22" },
{ "id": "23" },
{ "id": "24" },
{ "id": "25" },
{ "id": "26" },
{ "id": "27" },
{ "id": "28" },
{ "id": "29" },
{ "id": "30" },
{ "id": "31" },
{ "id": "32" },
{ "id": "33" }
],
"edges": [
{ "source": "0", "target": "1" },
{ "source": "0", "target": "2" },
{ "source": "0", "target": "3" },
{ "source": "0", "target": "4" },
{ "source": "0", "target": "5" },
{ "source": "0", "target": "7" },
{ "source": "0", "target": "8" },
{ "source": "0", "target": "9" },
{ "source": "0", "target": "10" },
{ "source": "0", "target": "11" },
{ "source": "0", "target": "13" },
{ "source": "0", "target": "14" },
{ "source": "0", "target": "15" },
{ "source": "0", "target": "16" },
{ "source": "2", "target": "3" },
{ "source": "4", "target": "5" },
{ "source": "4", "target": "6" },
{ "source": "5", "target": "6" },
{ "source": "7", "target": "13" },
{ "source": "8", "target": "14" },
{ "source": "9", "target": "10" },
{ "source": "10", "target": "22" },
{ "source": "10", "target": "14" },
{ "source": "10", "target": "12" },
{ "source": "10", "target": "24" },
{ "source": "10", "target": "21" },
{ "source": "10", "target": "20" },
{ "source": "11", "target": "24" },
{ "source": "11", "target": "22" },
{ "source": "11", "target": "14" },
{ "source": "12", "target": "13" },
{ "source": "16", "target": "17" },
{ "source": "16", "target": "18" },
{ "source": "16", "target": "21" },
{ "source": "16", "target": "22" },
{ "source": "17", "target": "18" },
{ "source": "17", "target": "20" },
{ "source": "18", "target": "19" },
{ "source": "19", "target": "20" },
{ "source": "19", "target": "33" },
{ "source": "19", "target": "22" },
{ "source": "19", "target": "23" },
{ "source": "20", "target": "21" },
{ "source": "21", "target": "22" },
{ "source": "22", "target": "24" },
{ "source": "22", "target": "25" },
{ "source": "22", "target": "26" },
{ "source": "22", "target": "23" },
{ "source": "22", "target": "28" },
{ "source": "22", "target": "30" },
{ "source": "22", "target": "31" },
{ "source": "22", "target": "32" },
{ "source": "22", "target": "33" },
{ "source": "23", "target": "28" },
{ "source": "23", "target": "27" },
{ "source": "23", "target": "29" },
{ "source": "23", "target": "30" },
{ "source": "23", "target": "31" },
{ "source": "23", "target": "33" },
{ "source": "32", "target": "33" }
]
},
"node": {
"style": {
"labelFill": "#fff",
"labelPlacement": "center"
}
},
"layout": { "type": "circular" }
}
83 changes: 83 additions & 0 deletions packages/g6-ssr/__tests__/test.spec.ts
Original file line number Diff line number Diff line change
@@ -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.exportToFile(join(__dirname, './assets/image'));

graph.destroy();
});

it('file pdf', async () => {
const graph = await fn('pdf');

graph.exportToFile(join(__dirname, '/assets/file'));

graph.destroy();
});

it('file svg', async () => {
const graph = await fn('svg');

expect(graph).toMatchFile('./assets/file.svg');

graph.exportToFile(join(__dirname, './assets/file'));

graph.destroy();
});
});
62 changes: 62 additions & 0 deletions packages/g6-ssr/bin/g6-ssr.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/* eslint-disable @typescript-eslint/no-var-requires */
/* eslint-disable no-console */
const cac = require('cac');
const fs = require('fs');
const { createGraph } = require('../dist/g6-ssr.cjs');
const { version } = require('../package.json');

const cli = cac();

cli.version(version);

cli.command('version', 'Show version').action(() => {
console.log(version);
});

cli
.command('export', 'Export G6 Options to Image, PDF or SVG')
.option('-i, --input <inputPath>', 'Path to the G6 options file')
.option('-o, --output <outputPath>', 'Path to the export file')
.option('-t, --type [type]', 'File type, default is image')
.action(async (options) => {
const { input, output, type } = options;

if (!input) {
console.log('\x1b[31m%s\x1b[0m', 'Please provide a path to the G6 options file');
process.exit(1);
}

if (!fs.existsSync(input)) {
console.log('\x1b[31m%s\x1b[0m', 'File does not exist: ', input);
process.exit(1);
}

let graphOptions;

try {
graphOptions = JSON.parse(fs.readFileSync(input, 'utf-8'));
} catch (e) {
console.log('\x1b[31m%s\x1b[0m', 'Invalid JSON file');
process.exit(1);
}

if (!graphOptions.outputType) {
if (type === 'svg' || type === 'pdf') {
graphOptions.outputType = type;
}
}

console.log(`Exporting to ${type || 'image'}...`);

const graph = await createGraph(graphOptions);

graph.exportToFile(output, type);

console.log('\x1b[32m%s\x1b[0m', 'Exported successfully!');

process.exit(0);
});

cli.help();

cli.parse();
11 changes: 11 additions & 0 deletions packages/g6-ssr/jest.config.js
Original file line number Diff line number Diff line change
@@ -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',
},
};
Loading

0 comments on commit e4ebb8d

Please sign in to comment.