├── .gitignore
├── .DS_Store
├── demo.gif
├── pages
├── index.js
├── component
│ ├── index.js
│ ├── nodeTooltip.js
│ ├── index.css
│ ├── edgTooltip.js
│ └── contextMenu.js
├── index.html
├── data.js
├── tree
│ └── index.js
├── tutorital.jsx
├── registerShape.js
└── app.js
├── .babelrc
├── tsconfig.json
├── README.md
├── package.json
└── webpack.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | pages/.umi/
3 | dist
--------------------------------------------------------------------------------
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/baizn/g6-in-react/HEAD/.DS_Store
--------------------------------------------------------------------------------
/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/baizn/g6-in-react/HEAD/demo.gif
--------------------------------------------------------------------------------
/pages/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './tutorital.jsx';
4 |
5 | ReactDOM.render(, document.getElementById('container'));
6 |
--------------------------------------------------------------------------------
/pages/component/index.js:
--------------------------------------------------------------------------------
1 | export { default as EdgeToolTips } from './edgTooltip'
2 | export { default as NodeTooltips } from './nodeTooltip'
3 | export { default as NodeContextMenu } from './contextMenu'
4 |
--------------------------------------------------------------------------------
/pages/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | G6 & React
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-env",
5 | {
6 | "targets": {
7 | "edge": 17,
8 | "firefox": 60,
9 | "chrome": 67,
10 | "safari": 11.1
11 | },
12 | "useBuiltIns": "usage"
13 | }
14 | ],
15 | "@babel/preset-react"
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "module": "esnext",
5 | "moduleResolution": "node",
6 | "importHelpers": true,
7 | "jsx": "preserve",
8 | "esModuleInterop": true,
9 | "sourceMap": true,
10 | "baseUrl": ".",
11 | "paths": {
12 | "@/*": ["pages/*"]
13 | },
14 | "allowSyntheticDefaultImports": true
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ### 如何在React中使用G6?
2 | 在平时的答疑工作中,经常会遇到「如何在React中使用G6」的问题,为了让G6用户能够在开发中很方便地接入G6,我们提供一个G6在React中使用的Demo,供开发者参考。
3 |
4 | `说明:鉴于目前React hooks比较🔥, 且大多数React开发者已经在使用hooks进行业务开发,因此,我们提供的Demo也是基于hooks的。`
5 |
6 | 更详细的内容请参考[React中使用G6](https://www.yuque.com/antv/g6/zmfur7)文档。
7 |
8 |
9 |
10 | ### 功能
11 | 我们提供的Demo,包括了以下功能:
12 | - 自定义节点;
13 | - 自定义边;
14 | - 节点的tooltip;
15 | - 边的tooltip;
16 | - 节点的ContextMenu;
17 | - tooltip及ContextMenu如何渲染自定义的React组件。
18 |
19 | ### Development
20 | ```
21 | # clone repo
22 | $ git clone https://github.com/baizn/g6-in-react.git
23 |
24 | # install dependencies
25 | $ npm install
26 |
27 | # start server
28 | $ npm start
29 |
30 | ```
--------------------------------------------------------------------------------
/pages/component/nodeTooltip.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Timeline } from 'antd'
3 | import 'antd/es/timeline/style/css'
4 | import styles from './index.css'
5 | const NodeToolTips = ({ x, y }) => {
6 | return (
7 |
8 |
9 | Create a services site 2015-09-01
10 | Solve initial network problems 2015-09-01
11 | Technical testing 2015-09-01
12 | Network problems being solved 2015-09-01
13 |
14 |
15 | )
16 | }
17 |
18 | export default NodeToolTips
19 |
--------------------------------------------------------------------------------
/pages/component/index.css:
--------------------------------------------------------------------------------
1 | .edgeTooltips {
2 | position: absolute;
3 | left: -300px;
4 | top: -100px;
5 | }
6 | .edgeTitle {
7 | position: relative;
8 | top: 0;
9 | left: 0;
10 | width: 150px;
11 | height: 70px;
12 | background: #fff;
13 | border: 1px solid #40a9ff;
14 | }
15 | .edgeDetail {
16 | position: relative;
17 | top: 0;
18 | left: 0;
19 | background: #bae7ff;
20 | width: 200px;
21 | }
22 | .tooltipsCommon {
23 | font-size: 16px;
24 | margin: 0;
25 | padding-left: 10px;
26 | }
27 | .tooltipsMoney {
28 | padding-bottom: 5px;
29 | }
30 | .tooltipsDate {
31 | font-size: 12px;
32 | color: #ccc;
33 | }
34 | .detailContent {
35 | margin-bottom: 10px;
36 | }
37 | .edgeCode {
38 | margin: 0;
39 | padding-left: 10px;
40 | font-size: 12px;
41 | }
42 | .edgeValue {
43 | padding-left: 10px;
44 | }
45 | .nodeTooltips {
46 | position: absolute;
47 | left: -300px;
48 | top: -100px;
49 | /* width: 150px; */
50 | height: 160px;
51 | padding: 10px;
52 | border: 1px solid #36cfc9;
53 | background: #e6fffb;
54 | }
--------------------------------------------------------------------------------
/pages/component/edgTooltip.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styles from './index.css'
3 |
4 | const EdgeToolTips = ({ x, y }) => {
5 | return (
6 |
7 |
8 |
凭证开立
9 |
1000,000,000元
10 |
2019-09-10
11 |
12 |
13 |
14 |
交易编码:
15 |
1000190203455
16 |
17 |
18 |
交易编码:
19 |
1000190203455
20 |
21 |
22 |
交易编码:
23 |
1000190203455
24 |
25 |
26 |
27 | )
28 | }
29 |
30 | export default EdgeToolTips
31 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "g6-in-react",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "webpack-dev-server --config webpack.config.js",
8 | "build": "webpack --config webpack.config.js --mode production",
9 | "test": "echo \"Error: no test specified\" && exit 1"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git+https://github.com/baizn/g6-in-react.git"
14 | },
15 | "keywords": [
16 | "g6"
17 | ],
18 | "author": "baizn",
19 | "license": "ISC",
20 | "bugs": {
21 | "url": "https://github.com/baizn/g6-in-react/issues"
22 | },
23 | "homepage": "https://github.com/baizn/g6-in-react#readme",
24 | "dependencies": {
25 | "@antv/g6": "^3.4.0",
26 | "antd": "^3.23.5",
27 | "html-webpack-plugin": "^4.2.0",
28 | "react": "^16.13.1",
29 | "react-dom": "^16.13.1",
30 | "webpack": "^4.42.1"
31 | },
32 | "devDependencies": {
33 | "@babel/preset-env": "^7.9.5",
34 | "@babel/preset-react": "^7.9.4",
35 | "@babel/core": "^7.9.6",
36 | "babel-loader": "^8.1.0",
37 | "css-loader": "^3.5.2",
38 | "postcss-loader": "^3.0.0",
39 | "style-loader": "^1.1.4",
40 | "ts-loader": "^7.0.0",
41 | "uglifyjs-webpack-plugin": "^2.2.0",
42 | "webpack-cli": "^3.3.11",
43 | "webpack-dev-server": "^3.10.3"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/pages/component/contextMenu.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Menu, Icon } from 'antd'
3 | import 'antd/es/menu/style/css'
4 | const { SubMenu } = Menu
5 |
6 | const NodeContextMenu = ({ x = -300, y = 0 }) => {
7 | return
57 | }
58 |
59 | export default NodeContextMenu
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const path = require('path')
3 | // eslint-disable-next-line prefer-destructuring
4 | const resolve = path.resolve;
5 | const HTMLPlugin = require('html-webpack-plugin');
6 | const UglifyJSWebpackPlguin = require('uglifyjs-webpack-plugin')
7 |
8 | module.exports = {
9 | entry: {
10 | g6InReact: [
11 | 'webpack-dev-server/client?http://localhost:8080',
12 | './pages/index.js'],
13 | },
14 | output: {
15 | filename: '[name].min.js',
16 | library: 'G6',
17 | libraryTarget: 'umd',
18 | libraryExport: 'default',
19 | path: resolve(process.cwd(), 'dist/'),
20 | },
21 | target: 'web',
22 | resolve: {
23 | // Add `.ts` as a resolvable extension.
24 | extensions: ['.ts', '.js'],
25 | },
26 | module: {
27 | rules: [
28 | {
29 | test: /\.(js|jsx)$/,
30 | use: {
31 | loader: 'babel-loader',
32 | options: {
33 | babelrc: true,
34 | },
35 | },
36 | },
37 | {
38 | test: /\.(ts|tsx)$/,
39 | use: {
40 | loader: 'ts-loader',
41 | options: {
42 | transpileOnly: true,
43 | },
44 | },
45 | },
46 | {
47 | test: /\.(css)$/,
48 | use: [
49 | 'style-loader',
50 | {
51 | loader: 'css-loader',
52 | options: {
53 | modules: true,
54 | },
55 | },
56 | ]
57 | }
58 | ],
59 | },
60 | plugins: [
61 | new webpack.NoEmitOnErrorsPlugin(),
62 | new webpack.optimize.AggressiveMergingPlugin(),
63 | new HTMLPlugin({
64 | template: './pages/index.html'
65 | }),
66 | // 更多配置项参考:https://www.webpackjs.com/plugins/uglifyjs-webpack-plugin/
67 | new UglifyJSWebpackPlguin({
68 | exclude: /node_modules/,
69 | uglifyOptions: {
70 | ecma: 5,
71 | ie8: true
72 | }
73 | })
74 | ],
75 | devtool: 'cheap-module-eval-source-map',
76 | devServer: {
77 | // 本地测试服务器加载的页面所在的目录,默认webpack-dev-server会为根文件夹提供本地服务器
78 | // contentBase: "./test-server",
79 | contentBase: './dist',
80 | // 监听的端口,默认为8080
81 | port: 8080,
82 | // 不跳转
83 | historyApiFallback: true
84 | }
85 | };
86 |
--------------------------------------------------------------------------------
/pages/data.js:
--------------------------------------------------------------------------------
1 | export const data = {
2 | nodes: [
3 | {
4 | id: '1',
5 | label: '公司1'
6 | },
7 | {
8 | id: '2',
9 | label: '公司2'
10 | },
11 | {
12 | id: '3',
13 | label: '公司3'
14 | },
15 | {
16 | id: '4',
17 | label: '公司4'
18 | },
19 | {
20 | id: '5',
21 | label: '公司5'
22 | },
23 | {
24 | id: '6',
25 | label: '公司6'
26 | },
27 | {
28 | id: '7',
29 | label: '公司7'
30 | },
31 | {
32 | id: '8',
33 | label: '公司8'
34 | },
35 | {
36 | id: '9',
37 | label: '公司9'
38 | }
39 | ],
40 | edges: [
41 | {
42 | source: '1',
43 | target: '2',
44 | data: {
45 | type: 'name1',
46 | amount: '100,000,000,00 元',
47 | date: '2019-08-03'
48 | }
49 | },
50 | {
51 | source: '1',
52 | target: '3',
53 | data: {
54 | type: 'name2',
55 | amount: '100,000,000,00 元',
56 | date: '2019-08-03'
57 | }
58 | },
59 | {
60 | source: '2',
61 | target: '5',
62 | data: {
63 | type: 'name1',
64 | amount: '100,000,000,00 元',
65 | date: '2019-08-03'
66 | }
67 | },
68 | {
69 | source: '5',
70 | target: '6',
71 | data: {
72 | type: 'name2',
73 | amount: '100,000,000,00 元',
74 | date: '2019-08-03'
75 | }
76 | },
77 | {
78 | source: '3',
79 | target: '4',
80 | data: {
81 | type: 'name3',
82 | amount: '100,000,000,00 元',
83 | date: '2019-08-03'
84 | }
85 | },
86 | {
87 | source: '4',
88 | target: '7',
89 | data: {
90 | type: 'name2',
91 | amount: '100,000,000,00 元',
92 | date: '2019-08-03'
93 | }
94 | },
95 | {
96 | source: '1',
97 | target: '8',
98 | data: {
99 | type: 'name2',
100 | amount: '100,000,000,00 元',
101 | date: '2019-08-03'
102 | }
103 | },
104 | {
105 | source: '1',
106 | target: '9',
107 | data: {
108 | type: 'name3',
109 | amount: '100,000,000,00 元',
110 | date: '2019-08-03'
111 | }
112 | }
113 | ]
114 | };
--------------------------------------------------------------------------------
/pages/tree/index.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { Button } from 'antd'
4 | import G6 from '@antv/g6';
5 |
6 | const treeData = {
7 | id: 'root',
8 | label: 'Root',
9 | children: [
10 | {
11 | id: 'SubTreeNode1',
12 | label: 'subroot1',
13 | children: [
14 | {
15 | id: 'SubTreeNode1.1',
16 | label: 'subroot1.1',
17 | }
18 | ]
19 | },
20 | {
21 | id: 'SubTreeNode2',
22 | label: 'subroot2',
23 | children: [
24 | {
25 | id: 'SubTreeNode2.1',
26 | label: 'subroot2.1',
27 | },
28 | {
29 | id: 'SubTreeNode2.2',
30 | label: 'subroot2.2',
31 | }
32 | ]
33 | }
34 | ]
35 | };
36 |
37 | const TreeGraphReact = () => {
38 | const ref = React.useRef(null)
39 | let graph = null
40 |
41 | useEffect(() => {
42 | if(!graph) {
43 |
44 | graph = new G6.TreeGraph({
45 | container: ref.current,
46 | width: 500,
47 | height: 500,
48 | modes: {
49 | default: ['drag-canvas']
50 | },
51 | defaultEdge: {
52 | shape: 'cubic-horizontal',
53 | style: {
54 | stroke: '#A3B1BF'
55 | }
56 | },
57 | defaultNode: {
58 | shape: 'rect',
59 | labelCfg: {
60 | style: {
61 | fill: '#000000A6',
62 | fontSize: 10
63 | }
64 | },
65 | style: {
66 | stroke: '#72CC4A',
67 | width: 150
68 | }
69 | },
70 | layout: {
71 | type: 'dendrogram', // 布局类型
72 | direction: 'LR', // 自左至右布局,可选的有 H / V / LR / RL / TB / BT
73 | nodeSep: 50, // 节点之间间距
74 | rankSep: 200 // 每个层级之间的间距
75 | }
76 | })
77 | }
78 | graph.data(treeData)
79 | graph.render()
80 | }, [])
81 |
82 | const handleChangeData = () => {
83 | const node = graph.findById('SubTreeNode1')
84 | graph.updateItem(node, {
85 | label: 'xxx',
86 | style: {
87 | fill: 'red'
88 | }
89 | })
90 | }
91 |
92 | return (
93 |
94 |
95 |
96 | );
97 | }
98 |
99 | export default TreeGraphReact
--------------------------------------------------------------------------------
/pages/tutorital.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react'
2 | import G6 from '@antv/g6';
3 |
4 | const data = {
5 | nodes: [
6 | { id: 'node0', size: 50 },
7 | { id: 'node1', size: 30 },
8 | { id: 'node2', size: 30 },
9 | { id: 'node3', size: 30 },
10 | { id: 'node4', size: 30 },
11 | { id: 'node5', size: 30 },
12 | { id: 'node6', size: 15 },
13 | { id: 'node7', size: 15 },
14 | { id: 'node8', size: 15 },
15 | { id: 'node9', size: 15 },
16 | { id: 'node10', size: 15 },
17 | { id: 'node11', size: 15 },
18 | { id: 'node12', size: 15 },
19 | { id: 'node13', size: 15 },
20 | { id: 'node14', size: 15 },
21 | { id: 'node15', size: 15 },
22 | { id: 'node16', size: 15 },
23 | ],
24 | edges: [
25 | { source: 'node0', target: 'node1' },
26 | { source: 'node0', target: 'node2' },
27 | { source: 'node0', target: 'node3' },
28 | { source: 'node0', target: 'node4' },
29 | { source: 'node0', target: 'node5' },
30 | { source: 'node1', target: 'node6' },
31 | { source: 'node1', target: 'node7' },
32 | { source: 'node2', target: 'node8' },
33 | { source: 'node2', target: 'node9' },
34 | { source: 'node2', target: 'node10' },
35 | { source: 'node2', target: 'node11' },
36 | { source: 'node2', target: 'node12' },
37 | { source: 'node2', target: 'node13' },
38 | { source: 'node3', target: 'node14' },
39 | { source: 'node3', target: 'node15' },
40 | { source: 'node3', target: 'node16' },
41 | ],
42 | };
43 |
44 | const Tutorital = () => {
45 | const ref = React.useRef(null)
46 | let graph = null
47 |
48 | useEffect(() => {
49 | if(!graph) {
50 | // 实例化 Minimap
51 | const minimap = new G6.Minimap()
52 |
53 | // 实例化 Graph
54 | graph = new G6.Graph({
55 | container: ref.current,
56 | width: 600,
57 | height: 400,
58 | plugins: [minimap],
59 | modes: {
60 | default: ['drag-canvas', 'zoom-canvas']
61 | },
62 | defaultNode: {
63 | type: 'circle',
64 | labelCfg: {
65 | style: {
66 | fill: '#000000A6',
67 | fontSize: 10
68 | }
69 | },
70 | style: {
71 | stroke: '#72CC4A',
72 | width: 150
73 | }
74 | },
75 | defaultEdge: {
76 | type: 'line'
77 | },
78 | layout: {
79 | type: 'force',
80 | preventOverlap: true,
81 | linkDistance: d => {
82 | if (d.source.id === 'node0') {
83 | return 100;
84 | }
85 | return 30;
86 | },
87 | },
88 | nodeStateStyles: {
89 | hover: {
90 | stroke: 'red',
91 | lineWidth: 3
92 | }
93 | },
94 | edgeStateStyles: {
95 | hover: {
96 | stroke: 'blue',
97 | lineWidth: 3
98 | }
99 | }
100 | })
101 | }
102 |
103 | graph.data(data)
104 |
105 | graph.render()
106 |
107 | graph.on('node:mouseenter', evt => {
108 | graph.setItemState(evt.item, 'hover', true)
109 | })
110 |
111 | graph.on('node:mouseleave', evt => {
112 | graph.setItemState(evt.item, 'hover', false)
113 | })
114 |
115 | graph.on('edge:mouseenter', evt => {
116 | graph.setItemState(evt.item, 'hover', true)
117 | })
118 |
119 | graph.on('edge:mouseleave', evt => {
120 | graph.setItemState(evt.item, 'hover', false)
121 | })
122 |
123 | }, [])
124 |
125 | return
126 | }
127 |
128 | export default Tutorital
129 |
--------------------------------------------------------------------------------
/pages/registerShape.js:
--------------------------------------------------------------------------------
1 | import G6 from '@antv/g6'
2 | const colorMap = {
3 | 'name1': '#72CC4A',
4 | 'name2': '#1A91FF',
5 | 'name3': '#FFAA15'
6 | }
7 |
8 | G6.registerNode('node', {
9 | drawShape: function drawShape(cfg, group) {
10 | const width = cfg.style.width;
11 | const stroke = cfg.style.stroke;
12 | const rect = group.addShape('rect', {
13 | attrs: {
14 | x: -width / 2,
15 | y: -15,
16 | width,
17 | height: 30,
18 | radius: 15,
19 | stroke,
20 | lineWidth: 0.6,
21 | fillOpacity: 1,
22 | fill: '#fff'
23 | }
24 | });
25 | const point1 = group.addShape('circle', {
26 | attrs: {
27 | x: -width / 2,
28 | y: 0,
29 | r: 3,
30 | fill: stroke
31 | }
32 | });
33 | const point2 = group.addShape('circle', {
34 | attrs: {
35 | x: width / 2,
36 | y: 0,
37 | r: 3,
38 | fill: stroke
39 | }
40 | });
41 | return rect;
42 | },
43 | getAnchorPoints: function getAnchorPoints() {
44 | return [[0, 0.5], [1, 0.5]];
45 | },
46 | update: function (cfg, item) {
47 | const group = item.getContainer()
48 | const children = group.get('children')
49 | const node = children[0]
50 | const circleLeft = children[1]
51 | const circleRight = children[2]
52 |
53 | const {style: {stroke}, labelStyle} = cfg
54 |
55 | if (stroke) {
56 | node.attr('stroke', stroke)
57 | circleLeft.attr('fill', stroke)
58 | circleRight.attr('fill', stroke)
59 | }
60 | }
61 | },
62 | 'single-shape'
63 | );
64 |
65 | G6.registerEdge('polyline', {
66 | itemType: 'edge',
67 | draw: function draw(cfg, group) {
68 | const startPoint = cfg.startPoint;
69 | const endPoint = cfg.endPoint;
70 |
71 | const Ydiff = endPoint.y - startPoint.y;
72 |
73 | const slope = Ydiff !== 0 ? 500 / Math.abs(Ydiff) : 0;
74 |
75 | const cpOffset = 16;
76 | const offset = Ydiff < 0 ? cpOffset : -cpOffset;
77 |
78 | const line1EndPoint = {
79 | x: startPoint.x + slope,
80 | y: endPoint.y + offset
81 | };
82 | const line2StartPoint = {
83 | x: line1EndPoint.x + cpOffset,
84 | y: endPoint.y
85 | };
86 |
87 | // 控制点坐标
88 | const controlPoint = {
89 | x:
90 | ((line1EndPoint.x - startPoint.x) * (endPoint.y - startPoint.y)) /
91 | (line1EndPoint.y - startPoint.y) +
92 | startPoint.x,
93 | y: endPoint.y
94 | };
95 |
96 | let path = [
97 | ['M', startPoint.x, startPoint.y],
98 | ['L', line1EndPoint.x, line1EndPoint.y],
99 | [
100 | 'Q',
101 | controlPoint.x,
102 | controlPoint.y,
103 | line2StartPoint.x,
104 | line2StartPoint.y
105 | ],
106 | ['L', endPoint.x, endPoint.y]
107 | ];
108 |
109 | if (Ydiff === 0) {
110 | path = [
111 | ['M', startPoint.x, startPoint.y],
112 | ['L', endPoint.x, endPoint.y]
113 | ];
114 | }
115 |
116 | const line = group.addShape('path', {
117 | attrs: {
118 | path,
119 | stroke: colorMap[cfg.data.type],
120 | lineWidth: 1.2,
121 | endArrow: false
122 | }
123 | });
124 |
125 | const labelLeftOffset = 8;
126 | const labelTopOffset = 8;
127 | // amount
128 | const amount = group.addShape('text', {
129 | attrs: {
130 | text: cfg.data.amount,
131 | x: line2StartPoint.x + labelLeftOffset,
132 | y: endPoint.y - labelTopOffset - 2,
133 | fontSize: 14,
134 | textAlign: 'left',
135 | textBaseline: 'middle',
136 | fill: '#000000D9'
137 | }
138 | });
139 | // type
140 | const type = group.addShape('text', {
141 | attrs: {
142 | text: cfg.data.type,
143 | x: line2StartPoint.x + labelLeftOffset,
144 | y: endPoint.y - labelTopOffset - amount.getBBox().height - 2,
145 | fontSize: 10,
146 | textAlign: 'left',
147 | textBaseline: 'middle',
148 | fill: '#000000D9'
149 | }
150 | });
151 | // date
152 | const date = group.addShape('text', {
153 | attrs: {
154 | text: cfg.data.date,
155 | x: line2StartPoint.x + labelLeftOffset,
156 | y: endPoint.y + labelTopOffset + 4,
157 | fontSize: 12,
158 | fontWeight: 300,
159 | textAlign: 'left',
160 | textBaseline: 'middle',
161 | fill: '#000000D9'
162 | }
163 | });
164 | return line;
165 | }
166 | });
--------------------------------------------------------------------------------
/pages/app.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import { data } from './data';
3 | import G6 from '@antv/g6';
4 | import { NodeTooltips, EdgeToolTips, NodeContextMenu } from './component'
5 | import './registerShape';
6 |
7 | export default function() {
8 | const ref = React.useRef(null)
9 | let graph = null
10 |
11 | // 边tooltip坐标
12 | const [showEdgeTooltip, setShowEdgeTooltip] = useState(false)
13 | const [edgeTooltipX, setEdgeTooltipX] = useState(0)
14 | const [edgeTooltipY, setEdgeTooltipY] = useState(0)
15 |
16 | // 节点tooltip坐标
17 | const [showNodeTooltip, setShowNodeTooltip] = useState(false)
18 | const [nodeTooltipX, setNodeToolTipX] = useState(0)
19 | const [nodeTooltipY, setNodeToolTipY] = useState(0)
20 |
21 | // 节点ContextMenu坐标
22 | const [showNodeContextMenu, setShowNodeContextMenu] = useState(false)
23 | const [nodeContextMenuX, setNodeContextMenuX] = useState(0)
24 | const [nodeContextMenuY, setNodeContextMenuY] = useState(0)
25 | const bindEvents = () => {
26 | // 监听edge上面mouse事件
27 | graph.on('edge:mouseenter', evt => {
28 | const { item, target } = evt
29 | debugger
30 | const type = target.get('type')
31 | if(type !== 'text') {
32 | return
33 | }
34 | const model = item.getModel()
35 | const { endPoint } = model
36 | // y=endPoint.y - height / 2,在同一水平线上,x值=endPoint.x - width - 10
37 | const y = endPoint.y - 35
38 | const x = endPoint.x - 150 - 10
39 | const point = graph.getCanvasByPoint(x, y)
40 | setEdgeTooltipX(point.x)
41 | setEdgeTooltipY(point.y)
42 | setShowEdgeTooltip(true)
43 | })
44 |
45 | graph.on('edge:mouseleave', () => {
46 | setShowEdgeTooltip(false)
47 | })
48 |
49 | // 监听node上面mouse事件
50 | graph.on('node:mouseenter', evt => {
51 | const { item } = evt
52 | const model = item.getModel()
53 | const { x, y } = model
54 | const point = graph.getCanvasByPoint(x, y)
55 |
56 | setNodeToolTipX(point.x - 75)
57 | setNodeToolTipY(point.y + 15)
58 | setShowNodeTooltip(true)
59 | })
60 |
61 | // 节点上面触发mouseleave事件后隐藏tooltip和ContextMenu
62 | graph.on('node:mouseleave', () => {
63 | setShowNodeTooltip(false)
64 | setShowNodeContextMenu(false)
65 | })
66 |
67 | // 监听节点上面右键菜单事件
68 | graph.on('node:contextmenu', evt => {
69 | const { item } = evt
70 | const model = item.getModel()
71 | const { x, y } = model
72 | const point = graph.getCanvasByPoint(x, y)
73 | setNodeContextMenuX(point.x)
74 | setNodeContextMenuY(point.y)
75 | setShowNodeContextMenu(true)
76 | })
77 | }
78 |
79 | useEffect(() => {
80 | if(!graph) {
81 | const miniMap = new G6.Minimap()
82 | graph = new G6.Graph({
83 | container: ref.current,
84 | width: 1200,
85 | height: 800,
86 | modes: {
87 | default: ['drag-canvas', 'drag-node']
88 | },
89 | defaultNode: {
90 | shape: 'node',
91 | // 节点文本样式
92 | labelCfg: {
93 | style: {
94 | fill: '#000000A6',
95 | fontSize: 10
96 | }
97 | },
98 | // 节点默认样式
99 | style: {
100 | stroke: '#72CC4A',
101 | width: 150
102 | }
103 | },
104 | defaultEdge: {
105 | shape: 'polyline'
106 | },
107 | // 节点交互状态配置
108 | nodeStateStyles: {
109 | hover: {
110 | stroke: 'red',
111 | lineWidth: 3
112 | }
113 | },
114 | edgeStateStyles: {
115 | hover: {
116 | stroke: 'blue',
117 | lineWidth: 3
118 | }
119 | },
120 | layout: {
121 | type: 'dagre',
122 | rankdir: 'LR',
123 | nodesep: 30,
124 | ranksep: 100
125 | },
126 | plugins: [miniMap]
127 | })
128 | }
129 |
130 | graph.data(data)
131 |
132 | graph.render()
133 |
134 | const edges = graph.getEdges()
135 | edges.forEach(edge => {
136 | const line = edge.getKeyShape()
137 | const stroke = line.attr('stroke')
138 | const targetNode = edge.getTarget()
139 | targetNode.update({
140 | style: { stroke }
141 | })
142 | })
143 | graph.paint()
144 |
145 | bindEvents()
146 | }, [])
147 |
148 | return (
149 |
150 | { showEdgeTooltip && }
151 | { showNodeTooltip && }
152 | { showNodeContextMenu && }
153 |
154 | );
155 | }
156 |
--------------------------------------------------------------------------------