mirror of
https://gitee.com/antv/g6.git
synced 2024-12-02 03:38:20 +08:00
feat: add react-node for v5 (#5079)
* chore: remove old react-node package * feat: upsertShape support create html element * feat(react-node): init react-node package * chore: update global config and ant-design/icons version * style: fix lint errors * refactor(react-node): adpapt to lastest react-g * fix: unregister dom-interaction plugin in transient & background canvases * chore: update demos * feat: support jsx node drag * docs: add demo * fix: fix type errors * refactor: add site demos * chore: update pnpm-lock * refactor: adapt to upsertShape adjust * chore: update latest g versions --------- Co-authored-by: yuqi.pyq <yuqi.pyq@antgroup.com>
This commit is contained in:
parent
4d5c037005
commit
0690995a0b
@ -11,8 +11,8 @@
|
||||
"build:site": "cd ./packages/site && npm run site:build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^17.7.1",
|
||||
"@commitlint/config-conventional": "^17.7.0",
|
||||
"@commitlint/cli": "^17.8.1",
|
||||
"@commitlint/config-conventional": "^17.8.1",
|
||||
"husky": "^8.0.3",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^2.8.8"
|
||||
|
@ -17,6 +17,7 @@
|
||||
"build:umd": "rimraf ./dist && rollup -c && npm run size",
|
||||
"build:cjs": "rimraf ./lib && tsc --module commonjs --outDir lib",
|
||||
"build:esm": "rimraf ./esm && tsc --module ESNext --outDir esm",
|
||||
"build:esm:watch": "rimraf ./esm && tsc --module ESNext --outDir esm --watch",
|
||||
"build": "run-p build:*",
|
||||
"bundle-vis": "cross-env BUNDLE_VIS=1 npm run build:umd",
|
||||
"size": "limit-size",
|
||||
@ -53,11 +54,11 @@
|
||||
"@ant-design/colors": "^7.0.0",
|
||||
"@antv/algorithm": "^0.1.26",
|
||||
"@antv/event-emitter": "latest",
|
||||
"@antv/g": "^5.18.17",
|
||||
"@antv/g-canvas": "^1.11.19",
|
||||
"@antv/g": "^5.18.23",
|
||||
"@antv/g-canvas": "^1.11.25",
|
||||
"@antv/g-plugin-3d": "^1.9.26",
|
||||
"@antv/g-plugin-control": "^1.9.17",
|
||||
"@antv/g-plugin-dragndrop": "^1.8.15",
|
||||
"@antv/g-plugin-dragndrop": "^1.8.17",
|
||||
"@antv/g-svg": "^1.10.21",
|
||||
"@antv/g-webgl": "^1.9.29",
|
||||
"@antv/graphlib": "^2.0.2",
|
||||
|
@ -167,6 +167,7 @@ export default abstract class Item implements IItem {
|
||||
enableBalanceShape,
|
||||
device: this.device,
|
||||
zoom: this.zoom,
|
||||
graph: this.graph,
|
||||
});
|
||||
}
|
||||
|
||||
@ -273,6 +274,7 @@ export default abstract class Item implements IItem {
|
||||
lodLevels: this.lodLevels,
|
||||
device: this.device,
|
||||
zoom: this.zoom,
|
||||
graph: this.graph,
|
||||
});
|
||||
} else {
|
||||
this.renderExt.themeStyles = this.themeStyles.default;
|
||||
|
@ -219,6 +219,9 @@ export class Graph<B extends BehaviorRegistry, T extends ThemeRegistry>
|
||||
width ?? this.container.scrollWidth,
|
||||
height ?? this.container.scrollHeight,
|
||||
pixelRatio,
|
||||
undefined,
|
||||
// enable dom interaction only for main canvas
|
||||
name === 'canvas' ? [] : ['dom-interaction'],
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -49,7 +49,7 @@ export abstract class BaseEdge {
|
||||
labelBackgroundShapeTransform?: string;
|
||||
};
|
||||
// cache the zoom level infomations
|
||||
private zoomCache: {
|
||||
#zoomCache: {
|
||||
// last responsed zoom ratio.
|
||||
zoom: number;
|
||||
// wordWrapWidth of labelShape according to the maxWidth
|
||||
@ -63,7 +63,7 @@ export abstract class BaseEdge {
|
||||
if (themeStyles) this.themeStyles = themeStyles;
|
||||
this.lodLevels = lodLevels;
|
||||
this.transformCache = {};
|
||||
this.zoomCache.zoom = zoom;
|
||||
this.#zoomCache.zoom = zoom;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -120,16 +120,16 @@ export abstract class BaseEdge {
|
||||
}
|
||||
});
|
||||
|
||||
const { zoom } = this.zoomCache;
|
||||
const { zoom } = this.#zoomCache;
|
||||
|
||||
const { maxWidth = '60%' } = this.mergedStyles.labelShape || {};
|
||||
this.zoomCache.wordWrapWidth = getWordWrapWidthByEnds(
|
||||
this.#zoomCache.wordWrapWidth = getWordWrapWidthByEnds(
|
||||
[this.sourcePoint, this.targetPoint],
|
||||
maxWidth,
|
||||
1,
|
||||
);
|
||||
|
||||
this.zoomCache.zoom = 1;
|
||||
this.#zoomCache.zoom = 1;
|
||||
if (zoom !== 1) this.onZoom(shapeMap, zoom);
|
||||
}
|
||||
|
||||
@ -288,9 +288,9 @@ export abstract class BaseEdge {
|
||||
const wordWrapWidth = getWordWrapWidthByEnds(
|
||||
[this.sourcePoint, this.targetPoint],
|
||||
maxWidth,
|
||||
this.zoomCache.zoom,
|
||||
this.#zoomCache.zoom,
|
||||
);
|
||||
this.zoomCache.wordWrapWidth = wordWrapWidth;
|
||||
this.#zoomCache.wordWrapWidth = wordWrapWidth;
|
||||
const style = {
|
||||
...this.defaultStyles.labelShape,
|
||||
textAlign: positionPreset.textAlign,
|
||||
|
@ -28,11 +28,13 @@ import {
|
||||
mergeStyles,
|
||||
upsertShape,
|
||||
} from '../../../util/shape';
|
||||
import type { IGraph } from '../../../types/graph';
|
||||
import { getWordWrapWidthByBox } from '../../../util/text';
|
||||
import { convertToNumber } from '../../../util/type';
|
||||
|
||||
export abstract class BaseNode {
|
||||
type: string;
|
||||
graph: IGraph;
|
||||
defaultStyles: NodeShapeStyles | ComboShapeStyles;
|
||||
themeStyles: NodeShapeStyles | ComboShapeStyles;
|
||||
mergedStyles: NodeShapeStyles | ComboShapeStyles;
|
||||
@ -40,13 +42,8 @@ export abstract class BaseNode {
|
||||
enableBalanceShape?: boolean;
|
||||
//vertex coordinate
|
||||
|
||||
/**
|
||||
* Cache the scale transform calculated by balancing size, for restoring.
|
||||
*/
|
||||
protected scaleTransformCache = '';
|
||||
|
||||
// cache the zoom level infomations
|
||||
protected zoomCache: {
|
||||
// cache the zoom level information
|
||||
#zoomCache: {
|
||||
// last response zoom ratio.
|
||||
zoom: number;
|
||||
// wordWrapWidth of labelShape according to the maxWidth
|
||||
@ -57,11 +54,13 @@ export abstract class BaseNode {
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
const { themeStyles, lodLevels, enableBalanceShape, zoom } = props;
|
||||
const { graph, themeStyles, lodLevels, enableBalanceShape, zoom } = props;
|
||||
|
||||
if (themeStyles) this.themeStyles = themeStyles;
|
||||
this.graph = graph;
|
||||
this.lodLevels = lodLevels;
|
||||
this.enableBalanceShape = enableBalanceShape;
|
||||
this.zoomCache.zoom = zoom;
|
||||
this.#zoomCache.zoom = zoom;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -133,7 +132,7 @@ export abstract class BaseNode {
|
||||
public updateCache(shapeMap) {
|
||||
if (shapeMap.labelShape) {
|
||||
const { maxWidth = '200%' } = this.mergedStyles.labelShape || {};
|
||||
this.zoomCache.wordWrapWidth = getWordWrapWidthByBox(
|
||||
this.#zoomCache.wordWrapWidth = getWordWrapWidthByBox(
|
||||
shapeMap.keyShape.getLocalBounds(),
|
||||
maxWidth,
|
||||
1,
|
||||
@ -255,7 +254,7 @@ export abstract class BaseNode {
|
||||
const wordWrapWidth = getWordWrapWidthByBox(
|
||||
keyShapeBox as AABB,
|
||||
maxWidth,
|
||||
this.zoomCache.zoom,
|
||||
this.#zoomCache.zoom,
|
||||
this.enableBalanceShape,
|
||||
);
|
||||
|
||||
@ -294,7 +293,8 @@ export abstract class BaseNode {
|
||||
positionPreset.textBaseline = 'middle';
|
||||
positionPreset.offsetX = 4;
|
||||
break;
|
||||
default: // at bottom by default
|
||||
default:
|
||||
// at bottom by default
|
||||
positionPreset.offsetY = 2;
|
||||
break;
|
||||
}
|
||||
@ -503,7 +503,7 @@ export abstract class BaseNode {
|
||||
const anchorPositionMap = this.calculateAnchorPosition(keyShapeStyle);
|
||||
individualConfigs.forEach((config, i) => {
|
||||
const { position, fill = keyShapeStyle.fill, ...style } = config;
|
||||
const [cx, cy] = this.getAnchorPosition(
|
||||
const [cx, cy] = this.#getAnchorPosition(
|
||||
position,
|
||||
anchorPositionMap,
|
||||
shapeMap,
|
||||
@ -530,7 +530,7 @@ export abstract class BaseNode {
|
||||
return shapes;
|
||||
}
|
||||
|
||||
private getAnchorPosition(
|
||||
#getAnchorPosition(
|
||||
position: string | [number, number],
|
||||
anchorPositionMap: IAnchorPositionMap,
|
||||
shapeMap: NodeShapeMap | ComboShapeMap,
|
||||
|
@ -1,26 +1,25 @@
|
||||
import { ID } from '@antv/graphlib';
|
||||
import {
|
||||
AABB,
|
||||
CircleStyleProps,
|
||||
RectStyleProps,
|
||||
DisplayObject,
|
||||
EllipseStyleProps,
|
||||
PolygonStyleProps,
|
||||
Group,
|
||||
HTMLStyleProps,
|
||||
IAnimation,
|
||||
ImageStyleProps,
|
||||
LineStyleProps,
|
||||
PathStyleProps,
|
||||
PolygonStyleProps,
|
||||
PolylineStyleProps,
|
||||
RectStyleProps,
|
||||
TextStyleProps,
|
||||
ImageStyleProps,
|
||||
Group,
|
||||
DisplayObject,
|
||||
IAnimation,
|
||||
Shape,
|
||||
} from '@antv/g';
|
||||
import {
|
||||
CubeGeometryProps,
|
||||
PlaneGeometryProps,
|
||||
SphereGeometryProps,
|
||||
TorusGeometryProps,
|
||||
} from '@antv/g-plugin-3d';
|
||||
import { ID } from '@antv/graphlib';
|
||||
import { AnimateCfg, IAnimates } from './animate';
|
||||
import {
|
||||
ComboDisplayModel,
|
||||
@ -58,6 +57,7 @@ export type GShapeStyle = CircleStyleProps &
|
||||
PathStyleProps &
|
||||
SphereGeometryProps &
|
||||
CubeGeometryProps &
|
||||
HTMLStyleProps &
|
||||
PlaneGeometryProps & {
|
||||
interactive?: boolean;
|
||||
};
|
||||
@ -114,7 +114,8 @@ export type SHAPE_TYPE =
|
||||
| 'line'
|
||||
| 'path'
|
||||
| 'text'
|
||||
| 'group';
|
||||
| 'group'
|
||||
| 'html';
|
||||
|
||||
export type SHAPE_TYPE_3D = 'sphere' | 'cube' | 'plane';
|
||||
|
||||
|
@ -22,6 +22,7 @@ export const createCanvas = (
|
||||
height: number,
|
||||
pixelRatio?: number,
|
||||
canvasConfig: Partial<CanvasConfig> = {},
|
||||
unregisterPlugins: string[] = [],
|
||||
): Canvas => {
|
||||
let renderer: any;
|
||||
switch (rendererType.toLowerCase()) {
|
||||
@ -46,6 +47,13 @@ export const createCanvas = (
|
||||
}),
|
||||
);
|
||||
|
||||
if (unregisterPlugins.length) {
|
||||
unregisterPlugins.forEach((name) => {
|
||||
const plugin = renderer.getPlugin(name);
|
||||
renderer.unregisterPlugin(plugin);
|
||||
});
|
||||
}
|
||||
|
||||
return new Canvas({
|
||||
container,
|
||||
width,
|
||||
|
@ -1,32 +1,25 @@
|
||||
import {
|
||||
AABB,
|
||||
Circle,
|
||||
DisplayObject,
|
||||
Ellipse,
|
||||
Group,
|
||||
HTML,
|
||||
IElement,
|
||||
Image,
|
||||
Line,
|
||||
Path,
|
||||
Polygon,
|
||||
Polyline,
|
||||
Rect,
|
||||
Text,
|
||||
Image,
|
||||
Path,
|
||||
AABB,
|
||||
Tuple3Number,
|
||||
} from '@antv/g';
|
||||
import { isArray, isNumber } from '@antv/util';
|
||||
import { DEFAULT_LABEL_BG_PADDING } from '../constant';
|
||||
import { Padding, Point, StandardPadding } from '../types/common';
|
||||
import { EdgeDisplayModel, EdgeModelData, EdgeShapeMap } from '../types/edge';
|
||||
import {
|
||||
GShapeStyle,
|
||||
SHAPE_TYPE,
|
||||
ItemShapeStyles,
|
||||
ShapeStyle,
|
||||
SHAPE_TYPE_3D,
|
||||
State,
|
||||
} from '../types/item';
|
||||
import { NodeDisplayModel, NodeModelData, NodeShapeMap } from '../types/node';
|
||||
import Combo from '../item/combo';
|
||||
import Edge from '../item/edge';
|
||||
import Node from '../item/node';
|
||||
import {
|
||||
AnimateTiming,
|
||||
ComboDisplayModel,
|
||||
@ -34,14 +27,21 @@ import {
|
||||
IAnimates,
|
||||
IGraph,
|
||||
} from '../types';
|
||||
import Node from '../item/node';
|
||||
import Edge from '../item/edge';
|
||||
import Combo from '../item/combo';
|
||||
import { Padding, Point, StandardPadding } from '../types/common';
|
||||
import { EdgeDisplayModel, EdgeModelData, EdgeShapeMap } from '../types/edge';
|
||||
import {
|
||||
GShapeStyle,
|
||||
ItemShapeStyles,
|
||||
SHAPE_TYPE,
|
||||
SHAPE_TYPE_3D,
|
||||
ShapeStyle,
|
||||
State,
|
||||
} from '../types/item';
|
||||
import { NodeDisplayModel, NodeModelData, NodeShapeMap } from '../types/node';
|
||||
import { getShapeAnimateBeginStyles } from './animate';
|
||||
import { isArrayOverlap } from './array';
|
||||
import { isBetween } from './math';
|
||||
import { cloneJSON } from './data';
|
||||
import { ComboShapeMap } from 'types/combo';
|
||||
import { isBetween } from './math';
|
||||
|
||||
export const ShapeTagMap = {
|
||||
circle: Circle,
|
||||
@ -54,6 +54,7 @@ export const ShapeTagMap = {
|
||||
image: Image,
|
||||
path: Path,
|
||||
group: Group,
|
||||
html: HTML,
|
||||
};
|
||||
|
||||
const LINE_TYPES = ['line', 'polyline', 'path'];
|
||||
|
9
packages/react-node/.dumirc.ts
Normal file
9
packages/react-node/.dumirc.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { defineConfig } from 'dumi';
|
||||
|
||||
export default defineConfig({
|
||||
outputPath: 'docs-dist',
|
||||
themeConfig: {
|
||||
name: '@antv/g6-react-node',
|
||||
},
|
||||
mfsu: false,
|
||||
});
|
3
packages/react-node/.editorconfig
Executable file → Normal file
3
packages/react-node/.editorconfig
Executable file → Normal file
@ -11,6 +11,3 @@ insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
|
3
packages/react-node/.eslintrc.js
Normal file
3
packages/react-node/.eslintrc.js
Normal file
@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
extends: require.resolve('@umijs/lint/dist/config/eslint'),
|
||||
};
|
@ -1,4 +1,6 @@
|
||||
export default {
|
||||
esm: 'rollup',
|
||||
cjs: 'rollup',
|
||||
};
|
||||
import { defineConfig } from 'father';
|
||||
|
||||
export default defineConfig({
|
||||
// more father config: https://github.com/umijs/father/blob/master/docs/config.md
|
||||
esm: { output: 'dist' },
|
||||
});
|
||||
|
24
packages/react-node/.gitignore
vendored
24
packages/react-node/.gitignore
vendored
@ -1,22 +1,6 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/npm-debug.log*
|
||||
/yarn-error.log
|
||||
/yarn.lock
|
||||
/package-lock.json
|
||||
|
||||
# production
|
||||
node_modules
|
||||
/dist
|
||||
/docs-dist
|
||||
|
||||
# misc
|
||||
.dumi/tmp
|
||||
.dumi/tmp-test
|
||||
.dumi/tmp-production
|
||||
.DS_Store
|
||||
|
||||
# umi
|
||||
.umi
|
||||
.umi-production
|
||||
.umi-test
|
||||
.env.local
|
||||
.dumi/tmp
|
@ -1,3 +0,0 @@
|
||||
docs
|
||||
src/.umi
|
||||
docs-dist
|
@ -1,7 +1,2 @@
|
||||
**/*.svg
|
||||
**/*.ejs
|
||||
**/*.html
|
||||
package.json
|
||||
.umi
|
||||
.umi-production
|
||||
.umi-test
|
||||
/dist
|
||||
*.yaml
|
||||
|
@ -1,11 +0,0 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"printWidth": 80,
|
||||
"overrides": [
|
||||
{
|
||||
"files": ".prettierrc",
|
||||
"options": { "parser": "json" }
|
||||
}
|
||||
]
|
||||
}
|
19
packages/react-node/.prettierrc.js
Normal file
19
packages/react-node/.prettierrc.js
Normal file
@ -0,0 +1,19 @@
|
||||
module.exports = {
|
||||
pluginSearchDirs: false,
|
||||
plugins: [
|
||||
require.resolve('prettier-plugin-organize-imports'),
|
||||
require.resolve('prettier-plugin-packagejson'),
|
||||
],
|
||||
printWidth: 80,
|
||||
proseWrap: 'never',
|
||||
singleQuote: true,
|
||||
trailingComma: 'all',
|
||||
overrides: [
|
||||
{
|
||||
files: '*.md',
|
||||
options: {
|
||||
proseWrap: 'preserve',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
3
packages/react-node/.stylelintrc
Normal file
3
packages/react-node/.stylelintrc
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "@umijs/lint/dist/config/stylelint"
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
import { defineConfig } from 'dumi';
|
||||
|
||||
export default defineConfig({
|
||||
title: 'G6 React Node',
|
||||
favicon:
|
||||
'https://gw.alipayobjects.com/zos/antfincdn/cfg5jFqgVt/DiceGraph.png',
|
||||
logo: 'https://gw.alipayobjects.com/zos/antfincdn/cfg5jFqgVt/DiceGraph.png',
|
||||
outputPath: 'docs',
|
||||
locales: [
|
||||
['zh-CN', '中文'],
|
||||
['en-US', 'English'],
|
||||
],
|
||||
resolve: { includes: ['docs', 'src'] },
|
||||
dynamicImport: {
|
||||
loading: '@/Loading',
|
||||
},
|
||||
// more config: https://d.umijs.org/config
|
||||
});
|
21
packages/react-node/LICENSE
Normal file
21
packages/react-node/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
@ -1,90 +1,40 @@
|
||||
# G6 React Node
|
||||
# @antv/g6-react-node
|
||||
|
||||
> Using React Component to custom your g6 node
|
||||
[![NPM version](https://img.shields.io/npm/v/@antv/g6-react-node.svg?style=flat)](https://npmjs.org/package/@antv/g6-react-node)
|
||||
[![NPM downloads](http://img.shields.io/npm/dm/@antv/g6-react-node.svg?style=flat)](https://npmjs.org/package/@antv/g6-react-node)
|
||||
|
||||
## Docs
|
||||
Using React Component to Define Your G6 Graph Node
|
||||
|
||||
[https://dicegraph.github.io/](https://dicegraph.github.io/g6-react-node)
|
||||
## Usage
|
||||
|
||||
## Example
|
||||
TODO
|
||||
|
||||
```jsx
|
||||
import {
|
||||
Group,
|
||||
Rect,
|
||||
Text,
|
||||
Circle,
|
||||
Image,
|
||||
createNodeFromReact,
|
||||
} from '@antv/g6-react-node';
|
||||
const ReactNode = ({ cfg = {} }) => {
|
||||
const { description, meta = {}, label = 'label' } = cfg;
|
||||
return (
|
||||
<Group>
|
||||
<Rect>
|
||||
<Rect
|
||||
style={{
|
||||
width: 150,
|
||||
height: 20,
|
||||
fill: cfg.color,
|
||||
radius: [6, 6, 0, 0],
|
||||
cursor: 'move',
|
||||
stroke: cfg.color,
|
||||
}}
|
||||
draggable
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
marginTop: 2,
|
||||
marginLeft: 75,
|
||||
textAlign: 'center',
|
||||
fontWeight: 'bold',
|
||||
fill: '#fff',
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
</Rect>
|
||||
<Rect
|
||||
style={{
|
||||
width: 150,
|
||||
height: 55,
|
||||
stroke: cfg.color,
|
||||
fill: '#ffffff',
|
||||
radius: [0, 0, 6, 6],
|
||||
}}
|
||||
>
|
||||
<Text style={{ marginTop: 5, fill: '#333', marginLeft: 4 }}>
|
||||
Desc: {description}
|
||||
</Text>
|
||||
<Text style={{ marginTop: 10, fill: '#333', marginLeft: 4 }}>
|
||||
Creator: {meta.creatorName}
|
||||
</Text>
|
||||
</Rect>
|
||||
</Rect>
|
||||
<Circle
|
||||
style={{
|
||||
stroke: cfg.color,
|
||||
r: 10,
|
||||
fill: '#fff',
|
||||
marginLeft: 75,
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
name="circle"
|
||||
>
|
||||
<Image
|
||||
style={{
|
||||
img:
|
||||
'https://gw.alipayobjects.com/zos/antfincdn/FLrTNDvlna/antv.png',
|
||||
width: 12,
|
||||
height: 12,
|
||||
marginLeft: 69,
|
||||
marginTop: -5,
|
||||
}}
|
||||
/>
|
||||
</Circle>
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
G6.registerNode('yourNode', createNodeFromReact(ReactNode));
|
||||
## Options
|
||||
|
||||
TODO
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
# install dependencies
|
||||
$ pnpm install
|
||||
|
||||
# develop library by docs demo
|
||||
$ pnpm start
|
||||
|
||||
# build library source code
|
||||
$ pnpm run build
|
||||
|
||||
# build library source code in watch mode
|
||||
$ pnpm run build:watch
|
||||
|
||||
# build docs
|
||||
$ pnpm run docs:build
|
||||
|
||||
# check your project for potential problems
|
||||
$ pnpm run doctor
|
||||
```
|
||||
|
||||
## LICENSE
|
||||
|
||||
MIT
|
||||
|
1
packages/react-node/docs/guide.md
Normal file
1
packages/react-node/docs/guide.md
Normal file
@ -0,0 +1 @@
|
||||
This is a guide example.
|
@ -1,174 +0,0 @@
|
||||
# Register Node Using React
|
||||
|
||||
How about building your G6 node using React Component with correct type inference.
|
||||
|
||||
```jsx
|
||||
import React from 'react';
|
||||
import G6 from '@antv/g6';
|
||||
import {
|
||||
Group,
|
||||
Rect,
|
||||
Text,
|
||||
Circle,
|
||||
Image,
|
||||
createNodeFromReact,
|
||||
} from '@antv/g6-react-node';
|
||||
import { G6MiniDemo } from '../src/ReactNode/demo';
|
||||
|
||||
const ReactNode = ({ cfg = {} }) => {
|
||||
const { description, meta = {}, label = 'label' } = cfg;
|
||||
return (
|
||||
<Group>
|
||||
<Rect>
|
||||
<Rect
|
||||
style={{
|
||||
width: 150,
|
||||
height: 20,
|
||||
fill: cfg.color,
|
||||
radius: [6, 6, 0, 0],
|
||||
cursor: 'move',
|
||||
stroke: cfg.color,
|
||||
}}
|
||||
draggable
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
marginTop: 2,
|
||||
marginLeft: 75,
|
||||
textAlign: 'center',
|
||||
fontWeight: 'bold',
|
||||
fill: '#fff',
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
</Rect>
|
||||
<Rect
|
||||
style={{
|
||||
width: 150,
|
||||
height: 55,
|
||||
stroke: cfg.color,
|
||||
fill: '#ffffff',
|
||||
radius: [0, 0, 6, 6],
|
||||
}}
|
||||
>
|
||||
<Text style={{ marginTop: 5, fill: '#333', marginLeft: 4 }}>
|
||||
Desc: {description}
|
||||
</Text>
|
||||
<Text style={{ marginTop: 10, fill: '#333', marginLeft: 4 }}>
|
||||
Creator: {meta.creatorName}
|
||||
</Text>
|
||||
</Rect>
|
||||
</Rect>
|
||||
<Circle
|
||||
style={{
|
||||
stroke: cfg.color,
|
||||
r: 10,
|
||||
fill: '#fff',
|
||||
marginLeft: 75,
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
name="circle"
|
||||
>
|
||||
<Image
|
||||
style={{
|
||||
img:
|
||||
'https://gw.alipayobjects.com/zos/antfincdn/FLrTNDvlna/antv.png',
|
||||
width: 12,
|
||||
height: 12,
|
||||
marginLeft: 69,
|
||||
marginTop: -5,
|
||||
}}
|
||||
/>
|
||||
</Circle>
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
|
||||
G6.registerNode('test', createNodeFromReact(ReactNode));
|
||||
|
||||
export default () => <G6MiniDemo nodeType="test" count={2} />;
|
||||
```
|
||||
|
||||
```jsx | pure
|
||||
import {
|
||||
Group,
|
||||
Rect,
|
||||
Text,
|
||||
Circle,
|
||||
Image,
|
||||
createNodeFromReact,
|
||||
} from '@antv/g6-react-node';
|
||||
|
||||
const ReactNode = ({ cfg = {} }) => {
|
||||
const { description, meta = {}, label = 'label' } = cfg;
|
||||
return (
|
||||
<Group>
|
||||
<Rect>
|
||||
<Rect
|
||||
style={{
|
||||
width: 150,
|
||||
height: 20,
|
||||
fill: cfg.color,
|
||||
radius: [6, 6, 0, 0],
|
||||
cursor: 'move',
|
||||
stroke: cfg.color,
|
||||
}}
|
||||
draggable
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
marginTop: 2,
|
||||
marginLeft: 75,
|
||||
textAlign: 'center',
|
||||
fontWeight: 'bold',
|
||||
fill: '#fff',
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
</Rect>
|
||||
<Rect
|
||||
style={{
|
||||
width: 150,
|
||||
height: 55,
|
||||
stroke: cfg.color,
|
||||
fill: '#ffffff',
|
||||
radius: [0, 0, 6, 6],
|
||||
}}
|
||||
>
|
||||
<Text style={{ marginTop: 5, fill: '#333', marginLeft: 4 }}>
|
||||
描述: {description}
|
||||
</Text>
|
||||
<Text style={{ marginTop: 10, fill: '#333', marginLeft: 4 }}>
|
||||
创建者: {meta.creatorName}
|
||||
</Text>
|
||||
</Rect>
|
||||
</Rect>
|
||||
<Circle
|
||||
style={{
|
||||
stroke: cfg.color,
|
||||
r: 10,
|
||||
fill: '#fff',
|
||||
marginLeft: 75,
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
name="circle"
|
||||
>
|
||||
<Image
|
||||
style={{
|
||||
img:
|
||||
'https://gw.alipayobjects.com/zos/antfincdn/FLrTNDvlna/antv.png',
|
||||
width: 12,
|
||||
height: 12,
|
||||
marginLeft: 69,
|
||||
marginTop: -5,
|
||||
}}
|
||||
/>
|
||||
</Circle>
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
|
||||
G6.registerNode('yourNode', createNodeFromReact(ReactNode));
|
||||
```
|
@ -1,170 +1,22 @@
|
||||
# 用 React 定义节点
|
||||
---
|
||||
hero:
|
||||
title: library
|
||||
description: Using React Component to Define Your G6 Graph Node
|
||||
actions:
|
||||
- text: Hello
|
||||
link: /
|
||||
- text: World
|
||||
link: /
|
||||
features:
|
||||
- title: Hello
|
||||
emoji: 💎
|
||||
description: Put hello description here
|
||||
- title: World
|
||||
emoji: 🌈
|
||||
description: Put world description here
|
||||
- title: '!'
|
||||
emoji: 🚀
|
||||
description: Put ! description here
|
||||
---
|
||||
|
||||
直接用 React 组件定义你的 G6 组件,自带类型提示。
|
||||
|
||||
```jsx
|
||||
import React from 'react';
|
||||
import G6 from '@antv/g6';
|
||||
import {
|
||||
Group,
|
||||
Rect,
|
||||
Text,
|
||||
Circle,
|
||||
Image,
|
||||
createNodeFromReact,
|
||||
} from '@antv/g6-react-node';
|
||||
import { G6MiniDemo } from '../src/ReactNode/demo';
|
||||
|
||||
const ReactNode = ({ cfg = {} }) => {
|
||||
const { description, meta = {}, label = 'label' } = cfg;
|
||||
return (
|
||||
<Group>
|
||||
<Rect>
|
||||
<Rect
|
||||
style={{
|
||||
width: 150,
|
||||
height: 20,
|
||||
fill: cfg.color,
|
||||
radius: [6, 6, 0, 0],
|
||||
cursor: 'move',
|
||||
stroke: cfg.color,
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
draggable
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
margin: [4, 5],
|
||||
fontWeight: 'bold',
|
||||
fill: '#fff',
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
</Rect>
|
||||
<Rect
|
||||
style={{
|
||||
width: 150,
|
||||
height: 55,
|
||||
stroke: cfg.color,
|
||||
fill: '#ffffff',
|
||||
radius: [0, 0, 6, 6],
|
||||
}}
|
||||
>
|
||||
<Text style={{ fill: '#333', margin: [8, 4] }}>
|
||||
描述: {description}
|
||||
</Text>
|
||||
<Text style={{ fill: '#333', margin: [6, 4] }}>
|
||||
创建者: {meta.creatorName}
|
||||
</Text>
|
||||
</Rect>
|
||||
</Rect>
|
||||
<Circle
|
||||
style={{
|
||||
stroke: cfg.color,
|
||||
r: 10,
|
||||
fill: '#fff',
|
||||
cursor: 'pointer',
|
||||
margin: [0, 'auto'],
|
||||
}}
|
||||
name="circle"
|
||||
>
|
||||
<Image
|
||||
style={{
|
||||
img:
|
||||
'https://gw.alipayobjects.com/zos/antfincdn/FLrTNDvlna/antv.png',
|
||||
width: 12,
|
||||
height: 12,
|
||||
margin: [4, 'auto'],
|
||||
}}
|
||||
/>
|
||||
</Circle>
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
|
||||
G6.registerNode('test', createNodeFromReact(ReactNode));
|
||||
|
||||
export default () => <G6MiniDemo nodeType="test" count={3} />;
|
||||
```
|
||||
|
||||
```jsx | pure
|
||||
import {
|
||||
Group,
|
||||
Rect,
|
||||
Text,
|
||||
Circle,
|
||||
Image,
|
||||
createNodeFromReact,
|
||||
} from '@antv/g6-react-node';
|
||||
|
||||
const ReactNode = ({ cfg = {} }) => {
|
||||
const { description, meta = {}, label = 'label' } = cfg;
|
||||
return (
|
||||
<Group>
|
||||
<Rect>
|
||||
<Rect
|
||||
style={{
|
||||
width: 150,
|
||||
height: 20,
|
||||
fill: cfg.color,
|
||||
radius: [6, 6, 0, 0],
|
||||
cursor: 'move',
|
||||
stroke: cfg.color,
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
draggable
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
margin: [4, 5],
|
||||
fontWeight: 'bold',
|
||||
fill: '#fff',
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
</Rect>
|
||||
<Rect
|
||||
style={{
|
||||
width: 150,
|
||||
height: 55,
|
||||
stroke: cfg.color,
|
||||
fill: '#ffffff',
|
||||
radius: [0, 0, 6, 6],
|
||||
}}
|
||||
>
|
||||
<Text style={{ marginTop: 5, fill: '#333', margin: [8, 4] }}>
|
||||
描述: {description}
|
||||
</Text>
|
||||
<Text style={{ marginTop: 10, fill: '#333', margin: [6, 4] }}>
|
||||
创建者: {meta.creatorName}
|
||||
</Text>
|
||||
</Rect>
|
||||
</Rect>
|
||||
<Circle
|
||||
style={{
|
||||
stroke: cfg.color,
|
||||
r: 10,
|
||||
fill: '#fff',
|
||||
cursor: 'pointer',
|
||||
margin: [0, 'auto'],
|
||||
}}
|
||||
name="circle"
|
||||
>
|
||||
<Image
|
||||
style={{
|
||||
img:
|
||||
'https://gw.alipayobjects.com/zos/antfincdn/FLrTNDvlna/antv.png',
|
||||
width: 12,
|
||||
height: 12,
|
||||
margin: [4, 'auto'],
|
||||
}}
|
||||
/>
|
||||
</Circle>
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
|
||||
G6.registerNode('yourNode', createNodeFromReact(ReactNode));
|
||||
```
|
||||
@antv/g6-react-node
|
||||
|
@ -1,47 +1,53 @@
|
||||
{
|
||||
"name": "@antv/g6-react-node",
|
||||
"version": "2.0.0-beta.0",
|
||||
"description": "Using React Component to Define Your G6 Graph Node",
|
||||
"version": "1.4.5",
|
||||
"scripts": {
|
||||
"start": "dumi dev",
|
||||
"build": "father-build",
|
||||
"deploy": "npm run docs:build && npm run docs:deploy",
|
||||
"release": "npm run build && npm publish",
|
||||
"prettier": "prettier --write \"**/*.{js,jsx,tsx,ts,less,md,json}\"",
|
||||
"docs:build": "dumi build",
|
||||
"docs:deploy": "gh-pages -d docs-dist",
|
||||
"test": "umi-test",
|
||||
"test:coverage": "umi-test --coverage"
|
||||
},
|
||||
"repository": "https://github.com/antvis/G6.git",
|
||||
"license": "MIT",
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.esm.js",
|
||||
"typings": "dist/index.d.ts",
|
||||
"gitHooks": {
|
||||
"pre-commit": "lint-staged"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,less,md,json}": [
|
||||
"prettier --write"
|
||||
],
|
||||
"*.ts?(x)": [
|
||||
"prettier --parser=typescript --write"
|
||||
]
|
||||
"module": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "father build",
|
||||
"build:watch": "father dev",
|
||||
"dev": "dumi dev",
|
||||
"docs:build": "dumi build",
|
||||
"doctor": "father doctor",
|
||||
"prepare": "dumi setup",
|
||||
"prepublishOnly": "father doctor && npm run build",
|
||||
"start": "npm run dev"
|
||||
},
|
||||
"dependencies": {
|
||||
"@antv/g6-core": "^0.0.7",
|
||||
"@antv/g-base": "^0.5.1",
|
||||
"@types/yoga-layout": "^1.9.3",
|
||||
"react": "^16.12.0",
|
||||
"yoga-layout-prebuilt": "^1.10.0"
|
||||
"@antv/react-g": "^1.10.19",
|
||||
"@antv/util": "^3.3.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@umijs/test": "^3.0.5",
|
||||
"dumi": "^1.1.1",
|
||||
"father-build": "^1.17.2",
|
||||
"gh-pages": "^3.0.0",
|
||||
"lint-staged": "^10.0.7",
|
||||
"prettier": "^2.8.8",
|
||||
"yorkie": "^2.0.0"
|
||||
}
|
||||
}
|
||||
"@ant-design/icons": "^5.2.6",
|
||||
"@antv/g-canvas": "^1.11.22",
|
||||
"@antv/g-svg": "^1.10.21",
|
||||
"@antv/g-webgl": "^1.9.29",
|
||||
"@antv/g6": "workspace:*",
|
||||
"@types/react": "^18.2.29",
|
||||
"@types/react-dom": "^18.2.14",
|
||||
"antd": "^5.10.2",
|
||||
"dumi": "^2.2.13",
|
||||
"father": "^4.3.5",
|
||||
"prettier": "^3.0.3",
|
||||
"prettier-plugin-organize-imports": "^3.2.3",
|
||||
"prettier-plugin-packagejson": "^2.4.6",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@antv/g": ">=5.18.23",
|
||||
"@antv/g6": "workspace:*",
|
||||
"react": ">=16.9.0",
|
||||
"react-dom": ">=16.9.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"authors": []
|
||||
}
|
||||
|
@ -1,5 +0,0 @@
|
||||
import { CircleStyle } from '../ReactNode/Shape/Circle';
|
||||
import React from 'react';
|
||||
|
||||
const Circle: React.FC<CircleStyle> = (props) => <div>{props}</div>;
|
||||
export default Circle;
|
@ -1,5 +0,0 @@
|
||||
import { EllipseStyle } from '../ReactNode/Shape/Ellipse';
|
||||
import React from 'react';
|
||||
|
||||
const Ellipse: React.FC<EllipseStyle> = (props) => <div>{props}</div>;
|
||||
export default Ellipse;
|
@ -1,5 +0,0 @@
|
||||
import React from 'react';
|
||||
import { EventAttrs } from '../Register/event';
|
||||
|
||||
const Event: React.FC<EventAttrs> = (props) => <div>{props}</div>;
|
||||
export default Event;
|
@ -1,5 +0,0 @@
|
||||
import { ImageStyle } from '../ReactNode/Shape/Image';
|
||||
import React from 'react';
|
||||
|
||||
const Image: React.FC<ImageStyle> = (props) => <div>{props}</div>;
|
||||
export default Image;
|
@ -1,5 +0,0 @@
|
||||
import { MarkerStyle } from '../ReactNode/Shape/Marker';
|
||||
import React from 'react';
|
||||
|
||||
const Marker: React.FC<MarkerStyle> = (props) => <div>{props}</div>;
|
||||
export default Marker;
|
@ -1,5 +0,0 @@
|
||||
import { PathStyle } from '../ReactNode/Shape/Path';
|
||||
import React from 'react';
|
||||
|
||||
const Path: React.FC<PathStyle> = (props) => <div>{props}</div>;
|
||||
export default Path;
|
@ -1,5 +0,0 @@
|
||||
import { PolygonStyle } from '../ReactNode/Shape/Polygon';
|
||||
import React from 'react';
|
||||
|
||||
const Polygon: React.FC<PolygonStyle> = (props) => <div>{props}</div>;
|
||||
export default Polygon;
|
@ -1,5 +0,0 @@
|
||||
import { RectStyle } from '../ReactNode/Shape/Rect';
|
||||
import React from 'react';
|
||||
|
||||
const Rect: React.FC<RectStyle> = (props) => <div {...props} />;
|
||||
export default Rect;
|
@ -1,5 +0,0 @@
|
||||
import { TextStyle } from '../ReactNode/Shape/Text';
|
||||
import React from 'react';
|
||||
|
||||
const Text: React.FC<TextStyle> = (props) => <div>{props}</div>;
|
||||
export default Text;
|
@ -1,28 +0,0 @@
|
||||
# Circle Style
|
||||
|
||||
```jsx
|
||||
import React from 'react';
|
||||
import G6 from '@antv/g6';
|
||||
import { Group, Circle, createNodeFromReact } from '@antv/g6-react-node';
|
||||
import { G6MiniDemo } from '../ReactNode/demo';
|
||||
|
||||
const ReactNode = ({ cfg = {} }) => (
|
||||
<Group>
|
||||
<Circle
|
||||
style={{
|
||||
r: 45,
|
||||
fill: cfg.color,
|
||||
radius: [6, 6, 0, 0],
|
||||
cursor: 'move',
|
||||
stroke: '#eee',
|
||||
}}
|
||||
/>
|
||||
</Group>
|
||||
);
|
||||
|
||||
G6.registerNode('test', createNodeFromReact(ReactNode));
|
||||
|
||||
export default () => <G6MiniDemo nodeType="test" count={2} />;
|
||||
```
|
||||
|
||||
<API src="./CircleStyle.tsx" ></API>
|
@ -1,29 +0,0 @@
|
||||
# 圆 (Circle) 样式属性
|
||||
|
||||
```jsx
|
||||
import React from 'react';
|
||||
import G6 from '@antv/g6';
|
||||
import { Group, Circle, createNodeFromReact } from '@antv/g6-react-node';
|
||||
import { G6MiniDemo } from '../ReactNode/demo';
|
||||
|
||||
const ReactNode = ({ cfg = {} }) => (
|
||||
<Group>
|
||||
<Circle
|
||||
style={{
|
||||
r: 45,
|
||||
fill: cfg.color,
|
||||
radius: [6, 6, 0, 0],
|
||||
cursor: 'move',
|
||||
stroke: '#eee',
|
||||
}}
|
||||
draggable
|
||||
/>
|
||||
</Group>
|
||||
);
|
||||
|
||||
G6.registerNode('test', createNodeFromReact(ReactNode));
|
||||
|
||||
export default () => <G6MiniDemo nodeType="test" count={2} />;
|
||||
```
|
||||
|
||||
<API src="./CircleStyle.tsx" ></API>
|
@ -1,29 +0,0 @@
|
||||
# Ellipse Style
|
||||
|
||||
```jsx
|
||||
import React from 'react';
|
||||
import G6 from '@antv/g6';
|
||||
import { Group, Ellipse, createNodeFromReact } from '@antv/g6-react-node';
|
||||
import { G6MiniDemo } from '../ReactNode/demo';
|
||||
|
||||
const ReactNode = ({ cfg = {} }) => (
|
||||
<Group>
|
||||
<Ellipse
|
||||
style={{
|
||||
rx: 45,
|
||||
ry: 20,
|
||||
fill: cfg.color,
|
||||
cursor: 'move',
|
||||
stroke: '#888',
|
||||
}}
|
||||
draggable
|
||||
/>
|
||||
</Group>
|
||||
);
|
||||
|
||||
G6.registerNode('test', createNodeFromReact(ReactNode));
|
||||
|
||||
export default () => <G6MiniDemo nodeType="test" count={2} />;
|
||||
```
|
||||
|
||||
<API src="./EllipseStyle.tsx" ></API>
|
@ -1,29 +0,0 @@
|
||||
# 椭圆 (Ellipse) 样式属性
|
||||
|
||||
```jsx
|
||||
import React from 'react';
|
||||
import G6 from '@antv/g6';
|
||||
import { Group, Ellipse, createNodeFromReact } from '@antv/g6-react-node';
|
||||
import { G6MiniDemo } from '../ReactNode/demo';
|
||||
|
||||
const ReactNode = ({ cfg = {} }) => (
|
||||
<Group>
|
||||
<Ellipse
|
||||
style={{
|
||||
rx: 45,
|
||||
ry: 20,
|
||||
fill: cfg.color,
|
||||
cursor: 'move',
|
||||
stroke: '#888',
|
||||
}}
|
||||
draggable
|
||||
/>
|
||||
</Group>
|
||||
);
|
||||
|
||||
G6.registerNode('test', createNodeFromReact(ReactNode));
|
||||
|
||||
export default () => <G6MiniDemo nodeType="test" count={2} />;
|
||||
```
|
||||
|
||||
<API src="./EllipseStyle.tsx" ></API>
|
@ -1,14 +0,0 @@
|
||||
# Event Props
|
||||
|
||||
Every Shape has a event to respond to it, which will not cause propagation.
|
||||
|
||||
```typescript
|
||||
type ShapeEventListner = (
|
||||
event: IG6GraphEvent,
|
||||
node: INode | null,
|
||||
shape: IShape,
|
||||
graph: Graph,
|
||||
) => void;
|
||||
```
|
||||
|
||||
<API src="./Event.tsx" ></API>
|
@ -1,14 +0,0 @@
|
||||
# 事件(Event)属性
|
||||
|
||||
每一个形状都会有单独的事件响应,他们之间不存在冒泡触发逻辑;
|
||||
|
||||
```typescript
|
||||
type ShapeEventListner = (
|
||||
event: IG6GraphEvent,
|
||||
node: INode | null,
|
||||
shape: IShape,
|
||||
graph: Graph,
|
||||
) => void;
|
||||
```
|
||||
|
||||
<API src="./Event.tsx" ></API>
|
@ -1,28 +0,0 @@
|
||||
# Image Style
|
||||
|
||||
```jsx
|
||||
import React from 'react';
|
||||
import G6 from '@antv/g6';
|
||||
import { Group, Image, createNodeFromReact } from '@antv/g6-react-node';
|
||||
import { G6MiniDemo } from '../ReactNode/demo';
|
||||
|
||||
const ReactNode = ({ cfg = {} }) => (
|
||||
<Group>
|
||||
<Image
|
||||
style={{
|
||||
img: 'https://gw.alipayobjects.com/zos/antfincdn/FLrTNDvlna/antv.png',
|
||||
width: 48,
|
||||
height: 48,
|
||||
cursor: 'move',
|
||||
}}
|
||||
draggable
|
||||
/>
|
||||
</Group>
|
||||
);
|
||||
|
||||
G6.registerNode('test', createNodeFromReact(ReactNode));
|
||||
|
||||
export default () => <G6MiniDemo nodeType="test" count={2} />;
|
||||
```
|
||||
|
||||
<API src="./ImageStyle.tsx" ></API>
|
@ -1,28 +0,0 @@
|
||||
# 图片 (Image) 样式属性
|
||||
|
||||
```jsx
|
||||
import React from 'react';
|
||||
import G6 from '@antv/g6';
|
||||
import { Group, Image, createNodeFromReact } from '@antv/g6-react-node';
|
||||
import { G6MiniDemo } from '../ReactNode/demo';
|
||||
|
||||
const ReactNode = ({ cfg = {} }) => (
|
||||
<Group>
|
||||
<Image
|
||||
style={{
|
||||
img: 'https://gw.alipayobjects.com/zos/antfincdn/FLrTNDvlna/antv.png',
|
||||
width: 48,
|
||||
height: 48,
|
||||
cursor: 'move',
|
||||
}}
|
||||
draggable
|
||||
/>
|
||||
</Group>
|
||||
);
|
||||
|
||||
G6.registerNode('test', createNodeFromReact(ReactNode));
|
||||
|
||||
export default () => <G6MiniDemo nodeType="test" count={2} />;
|
||||
```
|
||||
|
||||
<API src="./ImageStyle.tsx" ></API>
|
@ -1,5 +0,0 @@
|
||||
# Group & Common
|
||||
|
||||
Every component should be wrapped in a Group component, it's a way to group shape.
|
||||
|
||||
<API src="../ReactNode/Group.tsx"></API>
|
@ -1,5 +0,0 @@
|
||||
# 图形共有属性 与 Group(组)
|
||||
|
||||
我们建议每一个单元的图形,都应该有序的存放在一个 Group 里,一个节点组件,应该被 Group 包裹
|
||||
|
||||
<API src="../ReactNode/Group.tsx"></API>
|
@ -1,31 +0,0 @@
|
||||
# Marker Style
|
||||
|
||||
```jsx
|
||||
import React from 'react';
|
||||
import G6 from '@antv/g6';
|
||||
import { Group, Marker, createNodeFromReact } from '@antv/g6-react-node';
|
||||
import { G6MiniDemo } from '../ReactNode/demo';
|
||||
|
||||
const ReactNode = ({ cfg = {} }) => (
|
||||
<Group>
|
||||
<Marker
|
||||
style={{
|
||||
r: 40,
|
||||
symbol: function(x, y, r) {
|
||||
return [['M', x, y], ['L', x + r, y + r], ['L', x + r * 2, y], ['Z']];
|
||||
},
|
||||
fill: cfg.color,
|
||||
cursor: 'move',
|
||||
stroke: '#888',
|
||||
}}
|
||||
draggable
|
||||
/>
|
||||
</Group>
|
||||
);
|
||||
|
||||
G6.registerNode('test', createNodeFromReact(ReactNode));
|
||||
|
||||
export default () => <G6MiniDemo nodeType="test" count={2} />;
|
||||
```
|
||||
|
||||
<API src="./MarkerStyle.tsx" ></API>
|
@ -1,31 +0,0 @@
|
||||
# 标记 (Marker) 样式属性
|
||||
|
||||
```jsx
|
||||
import React from 'react';
|
||||
import G6 from '@antv/g6';
|
||||
import { Group, Marker, createNodeFromReact } from '@antv/g6-react-node';
|
||||
import { G6MiniDemo } from '../ReactNode/demo';
|
||||
|
||||
const ReactNode = ({ cfg = {} }) => (
|
||||
<Group>
|
||||
<Marker
|
||||
style={{
|
||||
r: 40,
|
||||
symbol: function(x, y, r) {
|
||||
return [['M', x, y], ['L', x + r, y + r], ['L', x + r * 2, y], ['Z']];
|
||||
},
|
||||
fill: cfg.color,
|
||||
cursor: 'move',
|
||||
stroke: '#888',
|
||||
}}
|
||||
draggable
|
||||
/>
|
||||
</Group>
|
||||
);
|
||||
|
||||
G6.registerNode('test', createNodeFromReact(ReactNode));
|
||||
|
||||
export default () => <G6MiniDemo nodeType="test" count={2} />;
|
||||
```
|
||||
|
||||
<API src="./MarkerStyle.tsx" ></API>
|
@ -1,34 +0,0 @@
|
||||
# Path Style
|
||||
|
||||
```jsx
|
||||
import React from 'react';
|
||||
import G6 from '@antv/g6';
|
||||
import { Group, Path, createNodeFromReact } from '@antv/g6-react-node';
|
||||
import { G6MiniDemo } from '../ReactNode/demo';
|
||||
|
||||
const ReactNode = ({ cfg = {} }) => (
|
||||
<Group>
|
||||
<Path
|
||||
style={{
|
||||
path: [
|
||||
['M', 0, 0],
|
||||
['L', 20, -7.5],
|
||||
['L', 13.33, 0],
|
||||
['L', 20, 7.5],
|
||||
['Z'],
|
||||
],
|
||||
fill: cfg.color,
|
||||
cursor: 'move',
|
||||
stroke: '#888',
|
||||
}}
|
||||
draggable
|
||||
/>
|
||||
</Group>
|
||||
);
|
||||
|
||||
G6.registerNode('test', createNodeFromReact(ReactNode));
|
||||
|
||||
export default () => <G6MiniDemo nodeType="test" count={2} />;
|
||||
```
|
||||
|
||||
<API src="./PathStyle.tsx" ></API>
|
@ -1,34 +0,0 @@
|
||||
# 路径 (Path) 样式属性
|
||||
|
||||
```jsx
|
||||
import React from 'react';
|
||||
import G6 from '@antv/g6';
|
||||
import { Group, Path, createNodeFromReact } from '@antv/g6-react-node';
|
||||
import { G6MiniDemo } from '../ReactNode/demo';
|
||||
|
||||
const ReactNode = ({ cfg = {} }) => (
|
||||
<Group>
|
||||
<Path
|
||||
style={{
|
||||
path: [
|
||||
['M', 0, 0],
|
||||
['L', 20, -7.5],
|
||||
['L', 13.33, 0],
|
||||
['L', 20, 7.5],
|
||||
['Z'],
|
||||
],
|
||||
fill: cfg.color,
|
||||
cursor: 'move',
|
||||
stroke: '#888',
|
||||
}}
|
||||
draggable
|
||||
/>
|
||||
</Group>
|
||||
);
|
||||
|
||||
G6.registerNode('test', createNodeFromReact(ReactNode));
|
||||
|
||||
export default () => <G6MiniDemo nodeType="test" count={2} />;
|
||||
```
|
||||
|
||||
<API src="./PathStyle.tsx" ></API>
|
@ -1,34 +0,0 @@
|
||||
# Polygon Style
|
||||
|
||||
```jsx
|
||||
import React from 'react';
|
||||
import G6 from '@antv/g6';
|
||||
import { Group, Polygon, createNodeFromReact } from '@antv/g6-react-node';
|
||||
import { G6MiniDemo } from '../ReactNode/demo';
|
||||
|
||||
const ReactNode = ({ cfg = {} }) => (
|
||||
<Group>
|
||||
<Polygon
|
||||
style={{
|
||||
points: [
|
||||
[0, 0],
|
||||
[10, 30],
|
||||
[50, 100],
|
||||
[30, 120],
|
||||
[-20, 80],
|
||||
],
|
||||
fill: cfg.color,
|
||||
cursor: 'move',
|
||||
stroke: '#888',
|
||||
}}
|
||||
draggable
|
||||
/>
|
||||
</Group>
|
||||
);
|
||||
|
||||
G6.registerNode('test', createNodeFromReact(ReactNode));
|
||||
|
||||
export default () => <G6MiniDemo nodeType="test" count={2} />;
|
||||
```
|
||||
|
||||
<API src="./PolygonStyle.tsx" ></API>
|
@ -1,34 +0,0 @@
|
||||
# 多边形 (Polygon) 样式属性
|
||||
|
||||
```jsx
|
||||
import React from 'react';
|
||||
import G6 from '@antv/g6';
|
||||
import { Group, Polygon, createNodeFromReact } from '@antv/g6-react-node';
|
||||
import { G6MiniDemo } from '../ReactNode/demo';
|
||||
|
||||
const ReactNode = ({ cfg = {} }) => (
|
||||
<Group>
|
||||
<Polygon
|
||||
style={{
|
||||
points: [
|
||||
[0, 0],
|
||||
[10, 30],
|
||||
[50, 100],
|
||||
[30, 120],
|
||||
[-20, 80],
|
||||
],
|
||||
fill: cfg.color,
|
||||
cursor: 'move',
|
||||
stroke: '#888',
|
||||
}}
|
||||
draggable
|
||||
/>
|
||||
</Group>
|
||||
);
|
||||
|
||||
G6.registerNode('test', createNodeFromReact(ReactNode));
|
||||
|
||||
export default () => <G6MiniDemo nodeType="test" count={2} />;
|
||||
```
|
||||
|
||||
<API src="./PolygonStyle.tsx" ></API>
|
@ -1,45 +0,0 @@
|
||||
# Rect Style
|
||||
|
||||
```jsx
|
||||
import React from 'react';
|
||||
import G6 from '@antv/g6';
|
||||
import { Group, Rect, createNodeFromReact } from '@antv/g6-react-node';
|
||||
import { G6MiniDemo } from '../ReactNode/demo';
|
||||
|
||||
const ReactNode = ({ cfg = {} }) => {
|
||||
const { description, meta = {}, label = 'label' } = cfg;
|
||||
return (
|
||||
<Group>
|
||||
<Rect>
|
||||
<Rect
|
||||
style={{
|
||||
width: 150,
|
||||
height: 20,
|
||||
fill: cfg.color,
|
||||
radius: [6, 6, 0, 0],
|
||||
cursor: 'move',
|
||||
stroke: cfg.color,
|
||||
}}
|
||||
draggable
|
||||
/>
|
||||
|
||||
<Rect
|
||||
style={{
|
||||
width: 150,
|
||||
height: 55,
|
||||
stroke: cfg.color,
|
||||
fill: '#ffffff',
|
||||
radius: [0, 0, 6, 6],
|
||||
}}
|
||||
/>
|
||||
</Rect>
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
|
||||
G6.registerNode('test', createNodeFromReact(ReactNode));
|
||||
|
||||
export default () => <G6MiniDemo nodeType="test" count={2} />;
|
||||
```
|
||||
|
||||
<API src="./RectStyle.tsx" ></API>
|
@ -1,45 +0,0 @@
|
||||
# 矩形 (Rect) 样式属性
|
||||
|
||||
```jsx
|
||||
import React from 'react';
|
||||
import G6 from '@antv/g6';
|
||||
import { Group, Rect, createNodeFromReact } from '@antv/g6-react-node';
|
||||
import { G6MiniDemo } from '../ReactNode/demo';
|
||||
|
||||
const ReactNode = ({ cfg = {} }) => {
|
||||
const { description, meta = {}, label = 'label' } = cfg;
|
||||
return (
|
||||
<Group>
|
||||
<Rect>
|
||||
<Rect
|
||||
style={{
|
||||
width: 150,
|
||||
height: 20,
|
||||
fill: cfg.color,
|
||||
radius: [6, 6, 0, 0],
|
||||
cursor: 'move',
|
||||
stroke: cfg.color,
|
||||
}}
|
||||
draggable
|
||||
/>
|
||||
|
||||
<Rect
|
||||
style={{
|
||||
width: 150,
|
||||
height: 55,
|
||||
stroke: cfg.color,
|
||||
fill: '#ffffff',
|
||||
radius: [0, 0, 6, 6],
|
||||
}}
|
||||
/>
|
||||
</Rect>
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
|
||||
G6.registerNode('test', createNodeFromReact(ReactNode));
|
||||
|
||||
export default () => <G6MiniDemo nodeType="test" count={2} />;
|
||||
```
|
||||
|
||||
<API src="./RectStyle.tsx" ></API>
|
@ -1,31 +0,0 @@
|
||||
# Text Style
|
||||
|
||||
```jsx
|
||||
import React from 'react';
|
||||
import G6 from '@antv/g6';
|
||||
import { Group, Text, createNodeFromReact } from '@antv/g6-react-node';
|
||||
import { G6MiniDemo } from '../ReactNode/demo';
|
||||
|
||||
const ReactNode = ({ cfg = {} }) => (
|
||||
<Group>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 25,
|
||||
fontWeight: 500,
|
||||
fill: cfg.color,
|
||||
cursor: 'move',
|
||||
stroke: '#888',
|
||||
}}
|
||||
draggable
|
||||
>
|
||||
Text
|
||||
</Text>
|
||||
</Group>
|
||||
);
|
||||
|
||||
G6.registerNode('test', createNodeFromReact(ReactNode));
|
||||
|
||||
export default () => <G6MiniDemo nodeType="test" count={2} />;
|
||||
```
|
||||
|
||||
<API src="./TextStyle.tsx" ></API>
|
@ -1,31 +0,0 @@
|
||||
# 文本 (Text) 样式属性
|
||||
|
||||
```jsx
|
||||
import React from 'react';
|
||||
import G6 from '@antv/g6';
|
||||
import { Group, Text, createNodeFromReact } from '@antv/g6-react-node';
|
||||
import { G6MiniDemo } from '../ReactNode/demo';
|
||||
|
||||
const ReactNode = ({ cfg = {} }) => (
|
||||
<Group>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: 25,
|
||||
fontWeight: 500,
|
||||
fill: cfg.color,
|
||||
cursor: 'move',
|
||||
stroke: '#888',
|
||||
}}
|
||||
draggable
|
||||
>
|
||||
Text
|
||||
</Text>
|
||||
</Group>
|
||||
);
|
||||
|
||||
G6.registerNode('test', createNodeFromReact(ReactNode));
|
||||
|
||||
export default () => <G6MiniDemo nodeType="test" count={2} />;
|
||||
```
|
||||
|
||||
<API src="./TextStyle.tsx" ></API>
|
@ -1,22 +0,0 @@
|
||||
import { AbstractShape, AnimateCfg } from '@antv/g-canvas';
|
||||
import { animations } from './animateFunc';
|
||||
|
||||
export type AnimationConfig = AnimateCfg & { animate: keyof typeof animations };
|
||||
|
||||
export const animateShapeWithConfig = (
|
||||
shape: AbstractShape,
|
||||
config?: Partial<AnimationConfig>,
|
||||
initMatrix?: number[],
|
||||
) => {
|
||||
const animateFunc = config?.animate && animations[config.animate];
|
||||
if (config && animateFunc) {
|
||||
const cfg = {
|
||||
duration: 2000,
|
||||
...config,
|
||||
initMatrix,
|
||||
};
|
||||
shape.animate(animateFunc, cfg);
|
||||
} else {
|
||||
shape.stopAnimate();
|
||||
}
|
||||
};
|
@ -1,158 +0,0 @@
|
||||
import { Util } from '@antv/g6-core';
|
||||
|
||||
type RatioUnit = [number, number] | [number[], number];
|
||||
|
||||
const getRatioByArray = (arr: RatioUnit[], ratio: number) => {
|
||||
let usingArr: [number, number][] = [];
|
||||
arr.forEach((item) => {
|
||||
if (item[0] instanceof Array) {
|
||||
item[0].forEach((subR) => usingArr.push([subR, item[1]]));
|
||||
} else {
|
||||
usingArr.push([Number(item[0]), item[1]]);
|
||||
}
|
||||
});
|
||||
usingArr = usingArr.sort((a, b) => a[0] - b[0]);
|
||||
for (let i = 0; i < usingArr.length; i++) {
|
||||
const now = usingArr[i];
|
||||
const next = usingArr[i + 1];
|
||||
if (!next) {
|
||||
return now[1];
|
||||
}
|
||||
if (ratio > now[0] && ratio <= next[0]) {
|
||||
const deltaRatio = ratio - now[0];
|
||||
const allRatio = next[0] - now[0];
|
||||
const deltaVal = next[1] - now[1];
|
||||
return now[1] + (deltaVal * deltaRatio) / allRatio;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const animations = {
|
||||
spin: (ratio: number) => {
|
||||
const toMatrix = Util.transform(
|
||||
[1, 0, 0, 0, 1, 0, 0, 0, 1],
|
||||
[['r', Math.PI * 2 * ratio]],
|
||||
);
|
||||
return {
|
||||
matrix: toMatrix,
|
||||
};
|
||||
},
|
||||
flash: (ratio: number) => ({ opacity: Math.abs(1 - ratio * 2) }),
|
||||
pulse: (ratio: number) => {
|
||||
const uRatio = getRatioByArray(
|
||||
[
|
||||
[[0, 1], 1],
|
||||
[0.5, 1.25],
|
||||
],
|
||||
ratio,
|
||||
);
|
||||
const toMatrix = Util.transform(
|
||||
[1, 0, 0, 0, 1, 0, 0, 0, 1],
|
||||
[['s', uRatio, uRatio]],
|
||||
);
|
||||
|
||||
return {
|
||||
matrix: toMatrix,
|
||||
};
|
||||
},
|
||||
rubber: (ratio: number) => {
|
||||
const xratio = getRatioByArray(
|
||||
[
|
||||
[0, 1],
|
||||
[0.3, 1.25],
|
||||
[0.4, 0.75],
|
||||
[0.5, 1.15],
|
||||
[0.65, 0.95],
|
||||
[0.75, 1.05],
|
||||
[1, 1],
|
||||
],
|
||||
ratio,
|
||||
);
|
||||
const yratio = getRatioByArray(
|
||||
[
|
||||
[0, 1],
|
||||
[0.3, 0.75],
|
||||
[0.4, 1.25],
|
||||
[0.5, 0.95],
|
||||
[0.65, 1.05],
|
||||
[0.75, 0.95],
|
||||
[1, 1],
|
||||
],
|
||||
ratio,
|
||||
);
|
||||
|
||||
const toMatrix = Util.transform(
|
||||
[1, 0, 0, 0, 1, 0, 0, 0, 1],
|
||||
[['s', xratio, yratio]],
|
||||
);
|
||||
|
||||
return {
|
||||
matrix: toMatrix,
|
||||
};
|
||||
},
|
||||
tada: (ratio: number) => {
|
||||
const scaleRatio = getRatioByArray(
|
||||
[
|
||||
[0, 1],
|
||||
[[0.1, 0.2], 0.9],
|
||||
[[0.8, 0.9], 1.1],
|
||||
[1, 1],
|
||||
],
|
||||
ratio,
|
||||
);
|
||||
const tadaRatio = getRatioByArray(
|
||||
[
|
||||
[0, 0],
|
||||
[[0.3, 0.5, 0.7, 0.9], 1],
|
||||
[[0.1, 0.2, 0.4, 0.6, 0.8], -1],
|
||||
[1, 0],
|
||||
],
|
||||
ratio,
|
||||
);
|
||||
|
||||
const toMatrix = Util.transform(
|
||||
[1, 0, 0, 0, 1, 0, 0, 0, 1],
|
||||
[
|
||||
['s', scaleRatio, scaleRatio],
|
||||
['r', ((tadaRatio || 0) * Math.PI) / 60],
|
||||
],
|
||||
);
|
||||
|
||||
return {
|
||||
matrix: toMatrix,
|
||||
};
|
||||
},
|
||||
bounce: (ratio: number) => {
|
||||
const yNum = getRatioByArray(
|
||||
[
|
||||
[[0, 1], 0],
|
||||
[[0.4, 0.43], -30],
|
||||
[0.7, -15],
|
||||
[0.8, 10],
|
||||
[0.9, -3],
|
||||
],
|
||||
ratio,
|
||||
);
|
||||
const scaleY = getRatioByArray(
|
||||
[
|
||||
[[0, 1], 1],
|
||||
[[0.4, 0.43], 1.1],
|
||||
[0.7, 1.05],
|
||||
[0.8, 0.95],
|
||||
[0.9, 1.02],
|
||||
],
|
||||
ratio,
|
||||
);
|
||||
const toMatrix = Util.transform(
|
||||
[1, 0, 0, 0, 1, 0, 0, 0, 1],
|
||||
[
|
||||
['t', 0, yNum],
|
||||
['s', 1, scaleY],
|
||||
],
|
||||
);
|
||||
|
||||
return {
|
||||
matrix: toMatrix,
|
||||
};
|
||||
},
|
||||
};
|
@ -1,92 +0,0 @@
|
||||
# 动画使用案例
|
||||
|
||||
> 这是一个简单的形状事件绑定案例, 点击右上角按钮展开节点
|
||||
|
||||
```jsx
|
||||
import React from 'react';
|
||||
import G6 from '@antv/g6';
|
||||
import {
|
||||
Rect,
|
||||
Text,
|
||||
Circle,
|
||||
Image,
|
||||
Group,
|
||||
createNodeFromReact,
|
||||
} from '@antv/g6-react-node';
|
||||
import { G6MiniDemo } from '../ReactNode/demo';
|
||||
|
||||
const Tag = ({ text, color }) => (
|
||||
<Rect
|
||||
style={{
|
||||
fill: color,
|
||||
padding: [5, 10],
|
||||
width: 'auto',
|
||||
radius: [4],
|
||||
margin: [0, 8],
|
||||
}}
|
||||
>
|
||||
<Text style={{ fill: '#fff', fontSize: 10 }}>{text}</Text>
|
||||
</Rect>
|
||||
);
|
||||
|
||||
const Card = ({ cfg }) => {
|
||||
const { animated = false } = cfg;
|
||||
|
||||
return (
|
||||
<Group draggable>
|
||||
<Rect
|
||||
style={{
|
||||
width: 400,
|
||||
height: 'auto',
|
||||
fill: '#fff',
|
||||
stroke: '#ddd',
|
||||
shadowColor: '#eee',
|
||||
shadowBlur: 30,
|
||||
radius: [8],
|
||||
justifyContent: 'center',
|
||||
padding: [18, 0],
|
||||
}}
|
||||
draggable
|
||||
animation={
|
||||
animated && {
|
||||
animate: 'rubber',
|
||||
repeat: true,
|
||||
duration: 2000,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fill: '#000',
|
||||
margin: [0, 24],
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
onClick={(evt, node, shape, graph) => {
|
||||
graph.updateItem(node, {
|
||||
animated: !animated,
|
||||
});
|
||||
}}
|
||||
animation={
|
||||
animated && {
|
||||
animate: 'flash',
|
||||
repeat: true,
|
||||
duration: 2000,
|
||||
}
|
||||
}
|
||||
>
|
||||
点我{animated ? '暂停' : '看'}动画
|
||||
</Text>
|
||||
<Text style={{ fill: '#ccc', fontSize: 12, margin: [12, 24] }}>
|
||||
我是一段特别特别特别特别特别特别特别长的描述
|
||||
</Text>
|
||||
</Rect>
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
|
||||
G6.registerNode('test', createNodeFromReact(Card));
|
||||
|
||||
export default () => <G6MiniDemo nodeType="test" count={1} height={400} />;
|
||||
```
|
@ -1,95 +0,0 @@
|
||||
# Simple Card Example
|
||||
|
||||
> This is a simple card example
|
||||
|
||||
```jsx
|
||||
import React from 'react';
|
||||
import G6 from '@antv/g6';
|
||||
import {
|
||||
Rect,
|
||||
Text,
|
||||
Circle,
|
||||
Image,
|
||||
Group,
|
||||
createNodeFromReact,
|
||||
} from '@antv/g6-react-node';
|
||||
import { G6MiniDemo } from '../ReactNode/demo';
|
||||
|
||||
const Tag = ({ text, color }) => (
|
||||
<Rect
|
||||
style={{
|
||||
fill: color,
|
||||
padding: [5, 10],
|
||||
width: 'auto',
|
||||
radius: [4],
|
||||
margin: [0, 8],
|
||||
}}
|
||||
>
|
||||
<Text style={{ fill: '#fff' }}>{text}</Text>
|
||||
</Rect>
|
||||
);
|
||||
|
||||
const Card = () => {
|
||||
return (
|
||||
<Group>
|
||||
<Rect
|
||||
style={{
|
||||
width: 400,
|
||||
height: 'auto',
|
||||
fill: '#fff',
|
||||
stroke: '#ddd',
|
||||
shadowColor: '#eee',
|
||||
shadowBlur: 30,
|
||||
radius: [8],
|
||||
justifyContent: 'center',
|
||||
padding: [18, 0],
|
||||
}}
|
||||
draggable
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fill: '#000',
|
||||
margin: [0, 24],
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
}}
|
||||
>
|
||||
This is a card
|
||||
</Text>
|
||||
<Text style={{ fill: '#ccc', fontSize: 12, margin: [12, 24] }}>
|
||||
I'm a very very very very very very very long description.
|
||||
</Text>
|
||||
<Image
|
||||
style={{
|
||||
img:
|
||||
'https://gw.alipayobjects.com/zos/antfincdn/aPkFc8Sj7n/method-draw-image.svg',
|
||||
width: 200,
|
||||
height: 200,
|
||||
margin: [24, 'auto'],
|
||||
}}
|
||||
/>
|
||||
<Rect style={{ width: 'auto', flexDirection: 'row', padding: [4, 12] }}>
|
||||
<Tag color="#66ccff" text="This" />
|
||||
<Tag color="#66ccff" text="is a card" />
|
||||
<Tag color="#66ccff" text="definitely" />
|
||||
<Tag color="#66ccff" text="Tag" />
|
||||
</Rect>
|
||||
<Circle
|
||||
style={{
|
||||
position: 'absolute',
|
||||
x: 380,
|
||||
y: 20,
|
||||
r: 5,
|
||||
fill: 'red',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
/>
|
||||
</Rect>
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
|
||||
G6.registerNode('test', createNodeFromReact(Card));
|
||||
|
||||
export default () => <G6MiniDemo nodeType="test" count={1} height={400} />;
|
||||
```
|
@ -1,94 +0,0 @@
|
||||
# 简单卡片案例
|
||||
|
||||
> 这是一个简单的卡片案例
|
||||
|
||||
```jsx
|
||||
import React from 'react';
|
||||
import G6 from '@antv/g6';
|
||||
import {
|
||||
Rect,
|
||||
Text,
|
||||
Circle,
|
||||
Image,
|
||||
Group,
|
||||
createNodeFromReact,
|
||||
} from '@antv/g6-react-node';
|
||||
import { G6MiniDemo } from '../ReactNode/demo';
|
||||
|
||||
const Tag = ({ text, color }) => (
|
||||
<Rect
|
||||
style={{
|
||||
fill: color,
|
||||
padding: [5, 10],
|
||||
width: 'auto',
|
||||
radius: [4],
|
||||
margin: [0, 8],
|
||||
}}
|
||||
>
|
||||
<Text style={{ fill: '#fff' }}>{text}</Text>
|
||||
</Rect>
|
||||
);
|
||||
|
||||
const Card = () => {
|
||||
return (
|
||||
<Group>
|
||||
<Rect
|
||||
style={{
|
||||
width: 400,
|
||||
height: 'auto',
|
||||
fill: '#fff',
|
||||
stroke: '#ddd',
|
||||
shadowColor: '#eee',
|
||||
shadowBlur: 30,
|
||||
radius: [8],
|
||||
justifyContent: 'center',
|
||||
padding: [18, 0],
|
||||
}}
|
||||
draggable
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fill: '#000',
|
||||
margin: [0, 24],
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
}}
|
||||
>
|
||||
这是一个卡片
|
||||
</Text>
|
||||
<Text style={{ fill: '#ccc', fontSize: 12, margin: [12, 24] }}>
|
||||
我是一段特别特别特别特别特别特别特别长的描述
|
||||
</Text>
|
||||
<Image
|
||||
style={{
|
||||
img:
|
||||
'https://gw.alipayobjects.com/zos/antfincdn/aPkFc8Sj7n/method-draw-image.svg',
|
||||
width: 200,
|
||||
height: 200,
|
||||
margin: [24, 'auto'],
|
||||
}}
|
||||
/>
|
||||
<Rect style={{ width: 'auto', flexDirection: 'row', padding: [4, 12] }}>
|
||||
{
|
||||
["我是", "很多个", "很多个的", "标签"].map(e => <Tag color="#66ccff" text={e} />)
|
||||
}
|
||||
</Rect>
|
||||
<Circle
|
||||
style={{
|
||||
position: 'absolute',
|
||||
x: 380,
|
||||
y: 20,
|
||||
r: 5,
|
||||
fill: 'red',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
/>
|
||||
</Rect>
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
|
||||
G6.registerNode('test', createNodeFromReact(Card));
|
||||
|
||||
export default () => <G6MiniDemo nodeType="test" count={1} height={400} />;
|
||||
```
|
@ -1,118 +0,0 @@
|
||||
# Event Usage Example
|
||||
|
||||
> This is a simple usage examplr with event
|
||||
|
||||
```jsx
|
||||
import React from 'react';
|
||||
import G6 from '@antv/g6';
|
||||
import {
|
||||
Rect,
|
||||
Text,
|
||||
Circle,
|
||||
Image,
|
||||
Group,
|
||||
createNodeFromReact,
|
||||
} from '@antv/g6-react-node';
|
||||
import { G6MiniDemo } from '../ReactNode/demo';
|
||||
|
||||
const Tag = ({ text, color }) => (
|
||||
<Rect
|
||||
style={{
|
||||
fill: color,
|
||||
padding: [5, 10],
|
||||
width: 'auto',
|
||||
radius: [4],
|
||||
margin: [0, 8],
|
||||
}}
|
||||
>
|
||||
<Text style={{ fill: '#fff' }}>{text}</Text>
|
||||
</Rect>
|
||||
);
|
||||
|
||||
const Card = ({ cfg }) => {
|
||||
const { collapsed = false } = cfg;
|
||||
|
||||
return (
|
||||
<Group draggable>
|
||||
<Rect
|
||||
style={{
|
||||
width: 400,
|
||||
height: 'auto',
|
||||
fill: '#fff',
|
||||
stroke: '#ddd',
|
||||
shadowColor: '#eee',
|
||||
shadowBlur: 30,
|
||||
radius: [8],
|
||||
justifyContent: 'center',
|
||||
padding: [18, 0],
|
||||
}}
|
||||
draggable
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fill: '#000',
|
||||
margin: [0, 24],
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
}}
|
||||
>
|
||||
这是一个卡片
|
||||
</Text>
|
||||
<Text style={{ fill: '#ccc', fontSize: 12, margin: [12, 24] }}>
|
||||
我是一段特别特别特别特别特别特别特别长的描述
|
||||
</Text>
|
||||
{collapsed && (
|
||||
<Group>
|
||||
<Image
|
||||
style={{
|
||||
img:
|
||||
'https://gw.alipayobjects.com/zos/antfincdn/aPkFc8Sj7n/method-draw-image.svg',
|
||||
width: 200,
|
||||
height: 200,
|
||||
margin: [24, 'auto'],
|
||||
}}
|
||||
/>
|
||||
<Rect
|
||||
style={{ width: 'auto', flexDirection: 'row', padding: [4, 12] }}
|
||||
>
|
||||
<Tag color="#66ccff" text="我是" />
|
||||
<Tag color="#66ccff" text="很多个" />
|
||||
<Tag color="#66ccff" text="很多个的" />
|
||||
<Tag color="#66ccff" text="标签" />
|
||||
</Rect>
|
||||
</Group>
|
||||
)}
|
||||
<Circle
|
||||
style={{
|
||||
position: 'absolute',
|
||||
x: 380,
|
||||
y: 20,
|
||||
r: 5,
|
||||
fill: collapsed ? 'blue' : 'green',
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fill: '#fff',
|
||||
fontSize: 10,
|
||||
margin: [-6, -3, 0],
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
onClick={(evt, node, shape, graph) => {
|
||||
graph.updateItem(node, {
|
||||
collapsed: !collapsed,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{collapsed ? '-' : '+'}
|
||||
</Text>
|
||||
</Circle>
|
||||
</Rect>
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
|
||||
G6.registerNode('test', createNodeFromReact(Card));
|
||||
|
||||
export default () => <G6MiniDemo nodeType="test" count={1} height={400} />;
|
||||
```
|
@ -1,118 +0,0 @@
|
||||
# 事件使用示例
|
||||
|
||||
> 这是一个简单的形状事件绑定案例, 点击右上角按钮展开节点
|
||||
|
||||
```jsx
|
||||
import React from 'react';
|
||||
import G6 from '@antv/g6';
|
||||
import {
|
||||
Rect,
|
||||
Text,
|
||||
Circle,
|
||||
Image,
|
||||
Group,
|
||||
createNodeFromReact,
|
||||
} from '@antv/g6-react-node';
|
||||
import { G6MiniDemo } from '../ReactNode/demo';
|
||||
|
||||
const Tag = ({ text, color }) => (
|
||||
<Rect
|
||||
style={{
|
||||
fill: color,
|
||||
padding: [5, 10],
|
||||
width: 'auto',
|
||||
radius: [4],
|
||||
margin: [0, 8],
|
||||
}}
|
||||
>
|
||||
<Text style={{ fill: '#fff', fontSize: 10 }}>{text}</Text>
|
||||
</Rect>
|
||||
);
|
||||
|
||||
const Card = ({ cfg }) => {
|
||||
const { collapsed = false } = cfg;
|
||||
|
||||
return (
|
||||
<Group draggable>
|
||||
<Rect
|
||||
style={{
|
||||
width: 400,
|
||||
height: 'auto',
|
||||
fill: '#fff',
|
||||
stroke: '#ddd',
|
||||
shadowColor: '#eee',
|
||||
shadowBlur: 30,
|
||||
radius: [8],
|
||||
justifyContent: 'center',
|
||||
padding: [18, 0],
|
||||
}}
|
||||
draggable
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fill: '#000',
|
||||
margin: [0, 24],
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
}}
|
||||
>
|
||||
这是一个卡片
|
||||
</Text>
|
||||
<Text style={{ fill: '#ccc', fontSize: 12, margin: [12, 24] }}>
|
||||
我是一段特别特别特别特别特别特别特别长的描述
|
||||
</Text>
|
||||
{collapsed && (
|
||||
<Group>
|
||||
<Image
|
||||
style={{
|
||||
img:
|
||||
'https://gw.alipayobjects.com/zos/antfincdn/aPkFc8Sj7n/method-draw-image.svg',
|
||||
width: 200,
|
||||
height: 200,
|
||||
margin: [24, 'auto'],
|
||||
}}
|
||||
/>
|
||||
<Rect
|
||||
style={{ width: 'auto', flexDirection: 'row', padding: [4, 12] }}
|
||||
>
|
||||
<Tag color="#66ccff" text="我是" />
|
||||
<Tag color="#66ccff" text="很多个" />
|
||||
<Tag color="#66ccff" text="很多个的" />
|
||||
<Tag color="#66ccff" text="标签" />
|
||||
</Rect>
|
||||
</Group>
|
||||
)}
|
||||
<Circle
|
||||
style={{
|
||||
position: 'absolute',
|
||||
x: 380,
|
||||
y: 20,
|
||||
r: 5,
|
||||
fill: collapsed ? 'blue' : 'green',
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fill: '#fff',
|
||||
fontSize: 10,
|
||||
margin: [-6, -3, 0],
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
onClick={(evt, node, shape, graph) => {
|
||||
graph.updateItem(node, {
|
||||
collapsed: !collapsed,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{collapsed ? '-' : '+'}
|
||||
</Text>
|
||||
</Circle>
|
||||
</Rect>
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
|
||||
G6.registerNode('test', createNodeFromReact(Card));
|
||||
|
||||
export default () => <G6MiniDemo nodeType="test" count={1} height={400} />;
|
||||
```
|
116
packages/react-node/src/GNode/index.md
Normal file
116
packages/react-node/src/GNode/index.md
Normal file
@ -0,0 +1,116 @@
|
||||
# React G Node
|
||||
|
||||
基于 @antv/react-g 的 G6 React Node 节点。
|
||||
|
||||
```jsx
|
||||
import { ArrowDownOutlined, ArrowUpOutlined } from '@ant-design/icons';
|
||||
import { Button, Card, Col, Row, Statistic } from 'antd';
|
||||
import { createReactGNode, Circle, Rect, Text } from '@antv/g6-react-node';
|
||||
import { Graph, extend, Extensions } from '@antv/g6';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import type { ReactNodeProps } from '@antv/g6-react-node';
|
||||
|
||||
const Node = ({ model }) => {
|
||||
const { data } = model;
|
||||
const { value } = data;
|
||||
const [showShadow, setShowShadow] = useState(false);
|
||||
return (
|
||||
<Rect
|
||||
width={50}
|
||||
height={50}
|
||||
shadowBlur={showShadow ? 10 : 0}
|
||||
shadowColor="#bebebe"
|
||||
lineWidth={showShadow}
|
||||
fill={'rgba(0, 255, 0, 0.5)'}
|
||||
onMouseenter={() => setShowShadow(true)}
|
||||
onMouseleave={() => setShowShadow(false)}
|
||||
>
|
||||
<Circle cx={25} cy={25} r={20 * value} fill="rgb(255, 255, 0, 0.9)" />
|
||||
<Text
|
||||
x={25}
|
||||
y={25}
|
||||
fill="red"
|
||||
text={value.toFixed(2)}
|
||||
textAlign="center"
|
||||
textBaseline="middle"
|
||||
/>
|
||||
</Rect>
|
||||
);
|
||||
};
|
||||
|
||||
export default () => {
|
||||
const ref = useRef();
|
||||
const graphRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!ref.current) return;
|
||||
const container = ref.current;
|
||||
const width = container.scrollWidth;
|
||||
const height = container.scrollHeight || 500;
|
||||
|
||||
const reactGNode = createReactGNode(Node);
|
||||
|
||||
const ExtendGraph = extend(Graph, {
|
||||
nodes: {
|
||||
'react-g-node': reactGNode,
|
||||
},
|
||||
});
|
||||
|
||||
graphRef.current = new ExtendGraph({
|
||||
container,
|
||||
width,
|
||||
height,
|
||||
modes: {
|
||||
default: [
|
||||
{
|
||||
type: 'drag-node',
|
||||
enableTransient: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
data: {
|
||||
nodes: [
|
||||
{ id: 'node0', data: { size: [50, 50], value: 0.5 } },
|
||||
{ id: 'node1', data: { size: [50, 50], value: 0.9 } },
|
||||
{ id: 'node2', data: { size: [50, 50], value: 0.7 } },
|
||||
],
|
||||
edges: [
|
||||
{ id: 'edge1', source: 'node0', target: 'node1', data: {} },
|
||||
{ id: 'edge2', source: 'node0', target: 'node2', data: {} },
|
||||
],
|
||||
},
|
||||
node: {
|
||||
type: 'react-g-node',
|
||||
otherShapes: {},
|
||||
},
|
||||
});
|
||||
|
||||
return () => {
|
||||
graphRef.current.destroy();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div style={{ width: '100%', height: 500 }}>
|
||||
<div>
|
||||
<Button
|
||||
onClick={() => {
|
||||
Array.from({ length: 3 }).forEach((_, i) => {
|
||||
graphRef.current.updateData('node', {
|
||||
id: `node${i}`,
|
||||
data: {
|
||||
size: [50, 50],
|
||||
value: Math.random() * 0.5 + 0.5,
|
||||
},
|
||||
});
|
||||
});
|
||||
}}
|
||||
>
|
||||
更新数据
|
||||
</Button>
|
||||
</div>
|
||||
<div ref={ref}></div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
3
packages/react-node/src/GNode/index.tsx
Normal file
3
packages/react-node/src/GNode/index.tsx
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './layouts';
|
||||
export { createReactGNode } from './node';
|
||||
export * from './shapes';
|
4
packages/react-node/src/GNode/layouts/index.ts
Normal file
4
packages/react-node/src/GNode/layouts/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import { register } from './register';
|
||||
export type { Layout } from './typings';
|
||||
|
||||
register();
|
11
packages/react-node/src/GNode/layouts/register.ts
Normal file
11
packages/react-node/src/GNode/layouts/register.ts
Normal file
@ -0,0 +1,11 @@
|
||||
// import { CSS } from '@antv/g';
|
||||
// import { Layout as BlockFlowLayout } from '@antv/g-layout-blocklike';
|
||||
// import { Plugin as PluginYoga } from '@antv/g-plugin-yoga';
|
||||
import { Renderer } from '../typings';
|
||||
|
||||
export const register = (renderer?: Renderer) => {
|
||||
// CSS.registerLayout('block', BlockFlowLayout);
|
||||
// if (renderer) {
|
||||
// renderer.registerPlugin(new PluginYoga({}));
|
||||
// }
|
||||
};
|
2
packages/react-node/src/GNode/layouts/typings.ts
Normal file
2
packages/react-node/src/GNode/layouts/typings.ts
Normal file
@ -0,0 +1,2 @@
|
||||
|
||||
export type Layout = 'block' | 'flex';
|
97
packages/react-node/src/GNode/node.tsx
Normal file
97
packages/react-node/src/GNode/node.tsx
Normal file
@ -0,0 +1,97 @@
|
||||
import type { DisplayObject } from '@antv/g';
|
||||
import type { ComboModelData, NodeDisplayModel, NodeModelData } from '@antv/g6';
|
||||
import { Extensions } from '@antv/g6';
|
||||
import type { State } from '@antv/g6/lib/types/item';
|
||||
import { NodeShapeMap } from '@antv/g6/lib/types/node';
|
||||
import { render } from '@antv/react-g';
|
||||
import React from 'react';
|
||||
|
||||
const Ctors = {
|
||||
rect: Extensions.RectNode,
|
||||
circle: Extensions.CircleNode,
|
||||
} as const;
|
||||
|
||||
export const createReactGNode = (
|
||||
Component: (props: {
|
||||
model?: NodeDisplayModel;
|
||||
states?: State[];
|
||||
}) => React.ReactElement,
|
||||
shape: keyof typeof Ctors = 'rect',
|
||||
): any => {
|
||||
const Ctor: any = Ctors[shape];
|
||||
class GNode extends Ctor {
|
||||
drawKeyShape(
|
||||
model: NodeDisplayModel,
|
||||
shapeMap: NodeShapeMap,
|
||||
diffData?:
|
||||
| {
|
||||
previous: NodeModelData | ComboModelData;
|
||||
current: NodeModelData | ComboModelData;
|
||||
}
|
||||
| undefined,
|
||||
diffState?: { previous: State[]; current: State[] } | undefined,
|
||||
): DisplayObject<any, any> {
|
||||
const { data } = model;
|
||||
const { size: [width, height] = [0, 0] } = data as any;
|
||||
return this.upsertShape(
|
||||
shape,
|
||||
'keyShape',
|
||||
{
|
||||
x: -width / 2,
|
||||
y: -height / 2,
|
||||
width,
|
||||
height,
|
||||
fill: 'opacity',
|
||||
stroke: 'opacity',
|
||||
lineWidth: 0,
|
||||
},
|
||||
{ shapeMap, model, diffData, diffState },
|
||||
);
|
||||
}
|
||||
drawOtherShapes(
|
||||
model: NodeDisplayModel,
|
||||
shapeMap: NodeShapeMap,
|
||||
diffData?:
|
||||
| {
|
||||
previous: NodeModelData | ComboModelData;
|
||||
current: NodeModelData | ComboModelData;
|
||||
}
|
||||
| undefined,
|
||||
diffState?: { previous: State[]; current: State[] } | undefined,
|
||||
): { [id: string]: DisplayObject<any, any> } {
|
||||
const { id, data } = model;
|
||||
const {
|
||||
size: [width, height],
|
||||
} = data as any;
|
||||
|
||||
const groupId = `${id}-group`;
|
||||
const group = this.upsertShape(
|
||||
'group',
|
||||
groupId,
|
||||
{
|
||||
x: -width / 2,
|
||||
y: -height / 2,
|
||||
width,
|
||||
height,
|
||||
},
|
||||
{ shapeMap, model, diffData, diffState },
|
||||
);
|
||||
group.isMutationObserved = true;
|
||||
group.addEventListener('DOMNodeInsertedIntoDocument', () => {
|
||||
const content = (
|
||||
<Component model={model} states={diffState?.current || []} />
|
||||
);
|
||||
render(content, group);
|
||||
});
|
||||
group.addEventListener('destroy', () => {
|
||||
group.removeAllEventListeners();
|
||||
});
|
||||
|
||||
return {
|
||||
[groupId]: group,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return GNode;
|
||||
};
|
29
packages/react-node/src/GNode/shapes.ts
Normal file
29
packages/react-node/src/GNode/shapes.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import {
|
||||
Arrow,
|
||||
Circle,
|
||||
Ellipse,
|
||||
Group,
|
||||
HTML,
|
||||
Image,
|
||||
Line,
|
||||
Path,
|
||||
Polygon,
|
||||
Polyline,
|
||||
Rect,
|
||||
Text,
|
||||
} from '@antv/react-g';
|
||||
|
||||
export {
|
||||
Arrow,
|
||||
Circle,
|
||||
Ellipse,
|
||||
Group,
|
||||
HTML,
|
||||
Image,
|
||||
Line,
|
||||
Path,
|
||||
Polygon,
|
||||
Polyline,
|
||||
Rect,
|
||||
Text,
|
||||
};
|
5
packages/react-node/src/GNode/typings.ts
Normal file
5
packages/react-node/src/GNode/typings.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import type { Renderer as CanvasRenderer } from '@antv/g-canvas';
|
||||
import type { Renderer as SVGRenderer } from '@antv/g-svg';
|
||||
import type { Renderer as WebGLRenderer } from '@antv/g-webgl';
|
||||
|
||||
export type Renderer = CanvasRenderer | SVGRenderer | WebGLRenderer;
|
@ -1,64 +0,0 @@
|
||||
import * as Yoga from 'yoga-layout-prebuilt';
|
||||
|
||||
export const LayoutAlignMap = {
|
||||
auto: Yoga.ALIGN_AUTO,
|
||||
baseline: Yoga.ALIGN_BASELINE,
|
||||
center: Yoga.ALIGN_CENTER,
|
||||
'flex-end': Yoga.ALIGN_FLEX_END,
|
||||
'flex-start': Yoga.ALIGN_FLEX_START,
|
||||
'space-around': Yoga.ALIGN_SPACE_AROUND,
|
||||
'space-between': Yoga.ALIGN_SPACE_BETWEEN,
|
||||
stretch: Yoga.ALIGN_STRETCH,
|
||||
};
|
||||
|
||||
export const DisplayMap = {
|
||||
none: Yoga.DISPLAY_NONE,
|
||||
flex: Yoga.DISPLAY_FLEX,
|
||||
};
|
||||
|
||||
export const FlexDirectionMap = {
|
||||
column: Yoga.FLEX_DIRECTION_COLUMN,
|
||||
'column-reverse': Yoga.FLEX_DIRECTION_COLUMN_REVERSE,
|
||||
row: Yoga.FLEX_DIRECTION_ROW,
|
||||
'row-reverse': Yoga.FLEX_DIRECTION_ROW_REVERSE,
|
||||
};
|
||||
|
||||
export const FlexWrapMap = {
|
||||
'no-wrap': Yoga.WRAP_NO_WRAP,
|
||||
wrap: Yoga.WRAP_WRAP,
|
||||
'wrap-reverse': Yoga.WRAP_WRAP_REVERSE,
|
||||
};
|
||||
|
||||
export const JustifyContentMap = {
|
||||
center: Yoga.JUSTIFY_CENTER,
|
||||
'flex-end': Yoga.JUSTIFY_FLEX_END,
|
||||
'flex-start': Yoga.JUSTIFY_FLEX_START,
|
||||
'space-around': Yoga.JUSTIFY_SPACE_AROUND,
|
||||
'space-between': Yoga.JUSTIFY_SPACE_BETWEEN,
|
||||
'space-evenly': Yoga.JUSTIFY_SPACE_EVENLY,
|
||||
};
|
||||
|
||||
export type NumberOrAuto = number | string | 'auto';
|
||||
|
||||
export interface LayoutAttrs {
|
||||
alignContent: keyof typeof LayoutAlignMap;
|
||||
alignItems: keyof typeof LayoutAlignMap;
|
||||
alignSelf: keyof typeof LayoutAlignMap;
|
||||
display: keyof typeof DisplayMap;
|
||||
flex: number;
|
||||
flexBasis: number | string;
|
||||
flexGrow: number;
|
||||
flexShrink: number;
|
||||
flexDirection: keyof typeof FlexDirectionMap;
|
||||
flexWrap: keyof typeof FlexWrapMap;
|
||||
height: NumberOrAuto;
|
||||
width: NumberOrAuto;
|
||||
justifyContent: keyof typeof JustifyContentMap;
|
||||
margin: NumberOrAuto | NumberOrAuto[];
|
||||
padding: number | string | (number | string)[];
|
||||
maxHeight: number;
|
||||
maxWidth: number;
|
||||
minHeight: number;
|
||||
minWidth: number;
|
||||
onClick: (e: Event) => void;
|
||||
}
|
@ -1,282 +0,0 @@
|
||||
import Yoga, { Node, YogaNode } from 'yoga-layout-prebuilt';
|
||||
import { RawNode } from '../Register/getDataFromReactNode';
|
||||
import getSizeOfShape from './getShapeSize';
|
||||
import {
|
||||
DisplayMap,
|
||||
FlexDirectionMap,
|
||||
FlexWrapMap,
|
||||
JustifyContentMap,
|
||||
LayoutAlignMap,
|
||||
} from './LayoutEnums';
|
||||
|
||||
const getFourFromNumOrArr = (target: string | number | (string | number)[]) => {
|
||||
if (target instanceof Array) {
|
||||
switch (target.length) {
|
||||
case 1:
|
||||
const m = target[0];
|
||||
return [m, m, m, m];
|
||||
case 2:
|
||||
const [tb, lr] = target;
|
||||
return [tb, lr, tb, lr];
|
||||
case 3:
|
||||
const [t, lar, b] = target;
|
||||
return [t, lar, b, lar];
|
||||
case 4:
|
||||
return target;
|
||||
default:
|
||||
return [0, 0, 0, 0];
|
||||
}
|
||||
} else {
|
||||
return [target, target, target, target];
|
||||
}
|
||||
};
|
||||
|
||||
const constructYogaNode = (node: RawNode) => {
|
||||
const yogaNode = Node.create();
|
||||
const style = node.attrs;
|
||||
if (style.position === 'absolute') {
|
||||
yogaNode.setWidth(0);
|
||||
yogaNode.setHeight(0);
|
||||
return yogaNode;
|
||||
}
|
||||
if (style.alignContent) {
|
||||
yogaNode.setAlignContent(LayoutAlignMap[style.alignContent]);
|
||||
}
|
||||
if (style.alignItems) {
|
||||
yogaNode.setAlignItems(LayoutAlignMap[style.alignItems]);
|
||||
}
|
||||
if (style.alignSelf) {
|
||||
yogaNode.setAlignSelf(LayoutAlignMap[style.alignSelf]);
|
||||
}
|
||||
if (style.display) {
|
||||
yogaNode.setDisplay(DisplayMap[style.display]);
|
||||
} else {
|
||||
yogaNode.setDisplay(DisplayMap['flex']);
|
||||
}
|
||||
if (style.flex) {
|
||||
yogaNode.setFlex(style.flex);
|
||||
}
|
||||
if (style.flexBasis) {
|
||||
yogaNode.setFlexBasis(style.flexBasis);
|
||||
}
|
||||
if (style.flexGrow) {
|
||||
yogaNode.setFlexGrow(style.flexGrow);
|
||||
}
|
||||
if (style.flexShrink) {
|
||||
yogaNode.setFlexShrink(style.flexShrink);
|
||||
}
|
||||
if (style.flexDirection) {
|
||||
yogaNode.setFlexDirection(FlexDirectionMap[style.flexDirection]);
|
||||
}
|
||||
if (style.flexWrap) {
|
||||
yogaNode.setFlexWrap(FlexWrapMap[style.flexWrap]);
|
||||
}
|
||||
if (style.justifyContent) {
|
||||
yogaNode.setJustifyContent(JustifyContentMap[style.justifyContent]);
|
||||
}
|
||||
if (style.maxHeight) {
|
||||
yogaNode.setMaxHeight(style.maxHeight);
|
||||
}
|
||||
if (style.minHeight) {
|
||||
yogaNode.setMinHeight(style.minHeight);
|
||||
}
|
||||
if (style.maxWidth) {
|
||||
yogaNode.setMaxWidth(style.maxWidth);
|
||||
}
|
||||
if (style.minWidth) {
|
||||
yogaNode.setMinWidth(style.minWidth);
|
||||
}
|
||||
if (style.height) {
|
||||
if (style.height === 'auto') {
|
||||
yogaNode.setHeightAuto();
|
||||
} else {
|
||||
yogaNode.setHeight(style.height);
|
||||
}
|
||||
}
|
||||
if (style.width) {
|
||||
if (style.width === 'auto') {
|
||||
yogaNode.setWidthAuto();
|
||||
} else {
|
||||
yogaNode.setWidth(style.width);
|
||||
}
|
||||
}
|
||||
if (style.margin) {
|
||||
const marginArray = getFourFromNumOrArr(style.margin);
|
||||
|
||||
if (marginArray[0] === 'auto') {
|
||||
yogaNode.setMarginAuto(Yoga.EDGE_TOP);
|
||||
} else {
|
||||
yogaNode.setMargin(Yoga.EDGE_TOP, Number(marginArray[0]));
|
||||
}
|
||||
|
||||
if (marginArray[1] === 'auto') {
|
||||
yogaNode.setMarginAuto(Yoga.EDGE_RIGHT);
|
||||
} else {
|
||||
yogaNode.setMargin(Yoga.EDGE_RIGHT, Number(marginArray[1]));
|
||||
}
|
||||
|
||||
if (marginArray[2] === 'auto') {
|
||||
yogaNode.setMarginAuto(Yoga.EDGE_BOTTOM);
|
||||
} else {
|
||||
yogaNode.setMargin(Yoga.EDGE_BOTTOM, Number(marginArray[2]));
|
||||
}
|
||||
|
||||
if (marginArray[3] === 'auto') {
|
||||
yogaNode.setMarginAuto(Yoga.EDGE_LEFT);
|
||||
} else {
|
||||
yogaNode.setMargin(Yoga.EDGE_LEFT, Number(marginArray[3]));
|
||||
}
|
||||
}
|
||||
|
||||
if (style.padding) {
|
||||
const paddingArray = getFourFromNumOrArr(style.padding);
|
||||
yogaNode.setPadding(Yoga.EDGE_TOP, paddingArray[0]);
|
||||
yogaNode.setPadding(Yoga.EDGE_RIGHT, paddingArray[1]);
|
||||
yogaNode.setPadding(Yoga.EDGE_BOTTOM, paddingArray[2]);
|
||||
yogaNode.setPadding(Yoga.EDGE_LEFT, paddingArray[3]);
|
||||
}
|
||||
|
||||
return yogaNode;
|
||||
};
|
||||
|
||||
export interface LayoutedNode extends RawNode {
|
||||
boundaryBox: {
|
||||
width: number;
|
||||
height: number;
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
children: LayoutedNode[];
|
||||
}
|
||||
|
||||
type ContainerNode = RawNode & {
|
||||
container: YogaNode;
|
||||
children: ContainerNode[];
|
||||
};
|
||||
|
||||
const constructNodes = (
|
||||
root: RawNode,
|
||||
basicContainer: YogaNode,
|
||||
): ContainerNode | null => {
|
||||
const childrenArr = [[root]];
|
||||
const parentArr: ContainerNode[] = [];
|
||||
let resultNode: ContainerNode | null = null;
|
||||
|
||||
while (childrenArr[0]) {
|
||||
const children = childrenArr.pop() || [];
|
||||
const parent = parentArr.pop();
|
||||
const newChilren: ContainerNode[] = [];
|
||||
|
||||
for (let i = 0; i < children?.length; i += 1) {
|
||||
const node = children[i];
|
||||
const size = getSizeOfShape(node.type, node.attrs);
|
||||
if (!node.attrs.width) {
|
||||
node.attrs.width = size.width || 0;
|
||||
}
|
||||
if (!node.attrs.height) {
|
||||
node.attrs.height = size.height || 0;
|
||||
}
|
||||
const containerNode: ContainerNode = {
|
||||
...node,
|
||||
container: constructYogaNode(node),
|
||||
children: [],
|
||||
};
|
||||
|
||||
if (node.children.length) {
|
||||
parentArr.push(containerNode);
|
||||
childrenArr.push(node.children);
|
||||
}
|
||||
|
||||
newChilren.push(containerNode);
|
||||
}
|
||||
|
||||
if (!parent) {
|
||||
resultNode = newChilren[0];
|
||||
basicContainer.insertChild(newChilren[0].container, 0);
|
||||
} else {
|
||||
parent.children = newChilren;
|
||||
for (let j = 0; j < parent.children.length; j += 1) {
|
||||
parent.container.insertChild(parent.children[j].container, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resultNode;
|
||||
};
|
||||
|
||||
const caculateNodes = (
|
||||
node: ContainerNode,
|
||||
parentBoundaryBox: {
|
||||
width: number;
|
||||
height: number;
|
||||
x: number;
|
||||
y: number;
|
||||
},
|
||||
): LayoutedNode => {
|
||||
const boundaryBox = {
|
||||
width: Number(node.attrs.width) || 0,
|
||||
height: Number(node.attrs.height) || 0,
|
||||
x: 0,
|
||||
y: 0,
|
||||
};
|
||||
let actualBondary = { ...boundaryBox };
|
||||
const { container, ...restNode } = node;
|
||||
|
||||
if (restNode.attrs.position === 'absolute') {
|
||||
boundaryBox.x = restNode.attrs.x;
|
||||
boundaryBox.y = restNode.attrs.y;
|
||||
actualBondary = boundaryBox;
|
||||
} else if (container) {
|
||||
const layout = container.getComputedLayout();
|
||||
boundaryBox.width = layout.width;
|
||||
boundaryBox.height = layout.height;
|
||||
boundaryBox.x = layout.left + parentBoundaryBox.x;
|
||||
boundaryBox.y = layout.top + parentBoundaryBox.y;
|
||||
actualBondary = { ...boundaryBox };
|
||||
if (['circle', 'ellipse'].includes(restNode.type)) {
|
||||
boundaryBox.x += boundaryBox.width / 2;
|
||||
boundaryBox.y += boundaryBox.height / 2;
|
||||
}
|
||||
if (restNode.type === 'text') {
|
||||
boundaryBox.y += boundaryBox.height;
|
||||
}
|
||||
}
|
||||
|
||||
const children: LayoutedNode[] = [];
|
||||
|
||||
for (let i = 0; i < restNode.children.length; i += 1) {
|
||||
children.push(caculateNodes(restNode.children[i], actualBondary));
|
||||
}
|
||||
|
||||
return {
|
||||
...restNode,
|
||||
attrs: {
|
||||
...restNode.attrs,
|
||||
...boundaryBox,
|
||||
},
|
||||
children,
|
||||
boundaryBox,
|
||||
};
|
||||
};
|
||||
|
||||
const getPositionUsingYoga = (root: RawNode): LayoutedNode => {
|
||||
const basicContainer = Node.create();
|
||||
|
||||
// init container
|
||||
basicContainer.setWidthAuto();
|
||||
basicContainer.setHeightAuto();
|
||||
const newNodes =
|
||||
constructNodes(root, basicContainer) ||
|
||||
({ ...root, container: basicContainer } as ContainerNode);
|
||||
basicContainer.calculateLayout();
|
||||
const result = caculateNodes(newNodes, {
|
||||
width: 0,
|
||||
height: 0,
|
||||
x: 0,
|
||||
y: 0,
|
||||
});
|
||||
basicContainer.freeRecursive();
|
||||
return result;
|
||||
};
|
||||
|
||||
export default getPositionUsingYoga;
|
@ -1,186 +0,0 @@
|
||||
import { GPath } from '@/ReactNode/Shape/common';
|
||||
|
||||
type SizeOfShape = {
|
||||
type: string;
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
|
||||
const XYPath = ['L', 'M', 'C', 'S', 'Q', 'T', 'V'];
|
||||
const xyPath = XYPath.map((e) => e.toLocaleLowerCase());
|
||||
|
||||
const convertPathToPoints = (path: GPath[]) => {
|
||||
const points: [number, number][] = [];
|
||||
path.forEach(function (seg) {
|
||||
const [command, ...numbers] = seg;
|
||||
let [lastPoint] = points.slice(-1);
|
||||
if (!lastPoint) {
|
||||
lastPoint = [0, 0];
|
||||
}
|
||||
if (XYPath.includes(command)) {
|
||||
const [x, y] = numbers.slice(-2);
|
||||
points.push([x, y]);
|
||||
} else if (xyPath.includes(command)) {
|
||||
const [x, y] = numbers.slice(-2);
|
||||
points.push([x + lastPoint[0], y + lastPoint[1]]);
|
||||
} else if (command === 'H') {
|
||||
const [x] = numbers.slice(-1);
|
||||
points.push([x, lastPoint[0]]);
|
||||
} else if (command === 'h') {
|
||||
const [x] = numbers.slice(-1);
|
||||
points.push([x + lastPoint[0], lastPoint[1]]);
|
||||
} else if (command === 'V') {
|
||||
const [y] = numbers.slice(-1);
|
||||
points.push([lastPoint[0], y]);
|
||||
} else if (command === 'v') {
|
||||
const [y] = numbers.slice(-1);
|
||||
points.push([lastPoint[0], lastPoint[1] + y]);
|
||||
}
|
||||
});
|
||||
return points;
|
||||
};
|
||||
|
||||
const getPointsSize = (points: [number, number][]) => {
|
||||
let [xmax, ymax, xmin, ymin] = [0, 0, 0, 0];
|
||||
|
||||
points.forEach(([x, y]) => {
|
||||
if (x > xmax) {
|
||||
xmax = x;
|
||||
}
|
||||
if (y > ymax) {
|
||||
ymax = y;
|
||||
}
|
||||
if (x < xmin) {
|
||||
xmin = x;
|
||||
}
|
||||
if (y < ymin) {
|
||||
ymin = y;
|
||||
}
|
||||
});
|
||||
|
||||
return [xmax - xmin, ymax - ymin];
|
||||
};
|
||||
|
||||
const canvasRef: {
|
||||
context?: CanvasRenderingContext2D;
|
||||
timeoutSig?: ReturnType<typeof setTimeout>;
|
||||
canvas?: HTMLCanvasElement;
|
||||
} = {};
|
||||
|
||||
const getCanvasContext = () => {
|
||||
let context: CanvasRenderingContext2D | null;
|
||||
if (canvasRef.context) {
|
||||
context = canvasRef.context;
|
||||
} else {
|
||||
const canvas = document?.createElement('canvas');
|
||||
if (!canvas) {
|
||||
return null;
|
||||
}
|
||||
context = canvas.getContext('2d');
|
||||
if (context) {
|
||||
canvasRef.canvas = canvas;
|
||||
canvasRef.context = context;
|
||||
}
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
const getTextSize = (
|
||||
text: string,
|
||||
attrs: {
|
||||
fontSize?: number;
|
||||
fontFamily?: string;
|
||||
lineHeight?: number;
|
||||
[key: string]: any;
|
||||
},
|
||||
) => {
|
||||
const textArr = text.split('\n');
|
||||
const height =
|
||||
(attrs.fontSize || 12) * textArr.length * (attrs.lineHeight || 1);
|
||||
// Try to get canvas to measure text
|
||||
const context = getCanvasContext();
|
||||
if (context) {
|
||||
context.font = `${attrs.fontWeight || 'normal'} ${attrs.fontSize || 12}px ${
|
||||
attrs.fontFamily || ''
|
||||
}`;
|
||||
let width = 0;
|
||||
for (let i = 0; i < textArr.length; i += 1) {
|
||||
width = Math.max(width, context.measureText(textArr[i]).width);
|
||||
}
|
||||
return [(width * (attrs.fontSize || 12)) / 10, height];
|
||||
}
|
||||
// fallback solution
|
||||
return [
|
||||
Math.max.apply(
|
||||
Math,
|
||||
textArr.map((str) => str.length * (attrs.fontSize || 12)),
|
||||
),
|
||||
height,
|
||||
];
|
||||
};
|
||||
|
||||
const getSizeOfShape = (
|
||||
type: string,
|
||||
attrs: Partial<{
|
||||
path: GPath[];
|
||||
width: number | string;
|
||||
height: number | string;
|
||||
r: number;
|
||||
rx: number;
|
||||
ry: number;
|
||||
points: [number, number][];
|
||||
text: string;
|
||||
}>,
|
||||
): SizeOfShape => {
|
||||
switch (type) {
|
||||
case 'rect':
|
||||
case 'image':
|
||||
return {
|
||||
type,
|
||||
width: Number(attrs.width) || 0,
|
||||
height: Number(attrs.height) || 0,
|
||||
};
|
||||
case 'circle':
|
||||
case 'marker':
|
||||
return {
|
||||
type,
|
||||
width: (attrs.r || 0) * 2,
|
||||
height: (attrs.r || 0) * 2,
|
||||
};
|
||||
case 'ellipse':
|
||||
return {
|
||||
type,
|
||||
width: (attrs.rx || 0) * 2,
|
||||
height: (attrs.ry || 0) * 2,
|
||||
};
|
||||
case 'path':
|
||||
const pathSize = getPointsSize(convertPathToPoints(attrs.path || []));
|
||||
return {
|
||||
type,
|
||||
width: pathSize[0],
|
||||
height: pathSize[1],
|
||||
};
|
||||
case 'polygon':
|
||||
const polygonSize = getPointsSize(attrs.points || []);
|
||||
return {
|
||||
type,
|
||||
width: polygonSize[0],
|
||||
height: polygonSize[1],
|
||||
};
|
||||
case 'text':
|
||||
const textSize = getTextSize(attrs.text || '', attrs);
|
||||
return {
|
||||
type,
|
||||
width: textSize[0],
|
||||
height: textSize[1],
|
||||
};
|
||||
default:
|
||||
return {
|
||||
type,
|
||||
width: 0,
|
||||
height: 0,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export default getSizeOfShape;
|
@ -1,23 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function Loading() {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
margin: 'auto',
|
||||
marginTop: 120,
|
||||
textAlign: 'center',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<img
|
||||
style={{ width: 120, height: 'auto' }}
|
||||
src="https://gw.alipayobjects.com/zos/antfincdn/cfg5jFqgVt/DiceGraph.png"
|
||||
/>
|
||||
<h3>L o a d i n g . . . </h3>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
import { EventAttrs } from '../Register/event';
|
||||
import { AnimationConfig } from '../Animation/animate';
|
||||
import React from 'react';
|
||||
|
||||
interface GroupProps {
|
||||
/**
|
||||
* @description.en-US The unique id of this group
|
||||
* @description.zh-CN 唯一id
|
||||
*/
|
||||
id?: string;
|
||||
/**
|
||||
* @description.en-US The name of the shape which can be not unique.
|
||||
* @description.zh-CN 图形名字,可以不唯一
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* @description.en-US Whether the group/shape is visible
|
||||
* @description.zh-CN 图形/组是否可见
|
||||
*/
|
||||
visible?: boolean;
|
||||
/**
|
||||
* @description.en-US Whether the group is capturable
|
||||
* @description.zh-CN 图形/组是否捕捉事件
|
||||
*/
|
||||
capture?: boolean;
|
||||
/**
|
||||
* @description.en-US Whether the group is allowed to response dragstart, drag, and dragend events.
|
||||
* @description.zh-CN 图形/组是否响应拖拽事件
|
||||
*/
|
||||
draggable?: boolean;
|
||||
/**
|
||||
* @description.en-US The visual layer index of the group
|
||||
* @description.zh-CN 图形/组的层级
|
||||
*/
|
||||
zIndex?: number;
|
||||
/**
|
||||
* @description.en-US animation config
|
||||
* @description.zh-CN 动画设置
|
||||
*/
|
||||
animation?: Partial<AnimationConfig>;
|
||||
/**
|
||||
* @description.en-US Nodes wrapped within the component
|
||||
* @description.zh-CN 组件内包装的节点
|
||||
*/
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export type CommonProps = GroupProps & EventAttrs;
|
||||
|
||||
const Group: React.FC<GroupProps> = (props) => {
|
||||
// @ts-ignore
|
||||
const { children, ...rest } = props;
|
||||
return (
|
||||
<div
|
||||
data-attr={{
|
||||
...rest,
|
||||
type: 'group',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Group;
|
@ -1,36 +0,0 @@
|
||||
import React from 'react';
|
||||
import { CommonProps } from '../Group';
|
||||
import { CommonShapeProps } from './common';
|
||||
|
||||
export interface CircleStyle extends CommonShapeProps {
|
||||
/**
|
||||
* @description.en-US The radius of the circle.
|
||||
* @description.zh-CN 圆的半径
|
||||
*/
|
||||
r: number;
|
||||
}
|
||||
|
||||
interface CircleProps extends CommonProps {
|
||||
/**
|
||||
* @description.en-US style of shape
|
||||
*/
|
||||
style: CircleStyle;
|
||||
}
|
||||
|
||||
const Circle: React.FC<CircleProps> = (props) => {
|
||||
// @ts-ignore
|
||||
const { children, ...rest } = props;
|
||||
|
||||
return (
|
||||
<div
|
||||
data-attr={{
|
||||
...rest,
|
||||
type: 'circle',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Circle;
|
@ -1,41 +0,0 @@
|
||||
import React from 'react';
|
||||
import { CommonProps } from '../Group';
|
||||
import { CommonShapeProps } from './common';
|
||||
|
||||
export interface EllipseStyle extends CommonShapeProps {
|
||||
/**
|
||||
* @description.en-US The horizontal raidus of the ellipse.
|
||||
* @description.zh-CN 椭圆的水平半径
|
||||
*/
|
||||
rx: number;
|
||||
/**
|
||||
* @description.en-US The vertical raidus of the ellipse.
|
||||
* @description.zh-CN 椭圆的纵向半径
|
||||
*/
|
||||
ry: number;
|
||||
}
|
||||
|
||||
interface EllipseProps extends CommonProps {
|
||||
/**
|
||||
* @description.en-US style of shape
|
||||
*/
|
||||
style: EllipseStyle;
|
||||
}
|
||||
|
||||
const Ellipse: React.FC<EllipseProps> = (props) => {
|
||||
// @ts-ignore
|
||||
const { children, ...rest } = props;
|
||||
|
||||
return (
|
||||
<div
|
||||
data-attr={{
|
||||
...rest,
|
||||
type: 'ellipse',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Ellipse;
|
@ -1,46 +0,0 @@
|
||||
import React from 'react';
|
||||
import { CommonProps } from '../Group';
|
||||
import { CommonShapeProps } from './common';
|
||||
|
||||
export interface ImageStyle extends CommonShapeProps {
|
||||
/**
|
||||
* @description.en-US The width of the image.
|
||||
* @description.zh-CN 图片宽度
|
||||
*/
|
||||
width?: number;
|
||||
/**
|
||||
* @description.en-US The height of the image.
|
||||
* @description.zh-CN 图片高度
|
||||
*/
|
||||
height?: number;
|
||||
/**
|
||||
* @description.en-US The img source of the image.
|
||||
* @description.zh-CN 图片数据源
|
||||
*/
|
||||
img: string | ImageData | CanvasImageData;
|
||||
}
|
||||
|
||||
interface ImageProps extends CommonProps {
|
||||
/**
|
||||
* @description.en-US style of shape
|
||||
*/
|
||||
style: ImageStyle;
|
||||
}
|
||||
|
||||
const Image: React.FC<ImageProps> = (props) => {
|
||||
// @ts-ignore
|
||||
const { children, ...rest } = props;
|
||||
|
||||
return (
|
||||
<div
|
||||
data-attr={{
|
||||
...rest,
|
||||
type: 'image',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Image;
|
@ -1,47 +0,0 @@
|
||||
import React from 'react';
|
||||
import { CommonProps } from '../Group';
|
||||
import { CommonShapeProps, GPath } from './common';
|
||||
|
||||
export interface MarkerStyle extends CommonShapeProps {
|
||||
/**
|
||||
* @description.en-US The radius of the marker.
|
||||
* @description.zh-CN 标记的半径
|
||||
*/
|
||||
r: number;
|
||||
/**
|
||||
* @description.en-US Built-in shapes or function return path array;
|
||||
* @description.zh-CN 内建标记 或者 生成标记路径的函数
|
||||
*/
|
||||
symbol:
|
||||
| 'circle'
|
||||
| 'square'
|
||||
| 'diamond'
|
||||
| 'triangle'
|
||||
| 'triangle-down'
|
||||
| ((x: number, y: number, r: number) => GPath[]);
|
||||
}
|
||||
|
||||
interface MarkerProps extends CommonProps {
|
||||
/**
|
||||
* @description.en-US style of shape
|
||||
*/
|
||||
style: MarkerStyle;
|
||||
}
|
||||
|
||||
const Marker: React.FC<MarkerProps> = (props) => {
|
||||
// @ts-ignore
|
||||
const { children, ...rest } = props;
|
||||
|
||||
return (
|
||||
<div
|
||||
data-attr={{
|
||||
...rest,
|
||||
type: 'marker',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Marker;
|
@ -1,68 +0,0 @@
|
||||
import React from 'react';
|
||||
import { CommonProps } from '../Group';
|
||||
import { Arrow, CommonShapeProps, GPath } from './common';
|
||||
|
||||
export interface PathStyle extends CommonShapeProps {
|
||||
/**
|
||||
* @description.en-US SVG like Path array.
|
||||
* @description.zh-CN G使用的SVG路径数组,参考SVG路径
|
||||
*/
|
||||
path: GPath[];
|
||||
/**
|
||||
* @description.en-US Show the arrow on the start of the path.
|
||||
* @description.zh-CN 开头的箭头,可以设置SVG路径字符串
|
||||
*/
|
||||
startArrow?: Arrow;
|
||||
/**
|
||||
* @description.en-US Show the arrow on the end of the path.
|
||||
* @description.zh-CN 结尾的箭头,可以设置SVG路径字符串
|
||||
*/
|
||||
endArrow?: Arrow;
|
||||
/**
|
||||
* @description.en-US The hitting area of the path. Enlarge the hitting area by enlarging its value.
|
||||
* @description.zh-CN 路径响应事件宽度。
|
||||
*/
|
||||
lineAppendWidth?: number;
|
||||
/**
|
||||
* @description.en-US The style of two ends of the path.
|
||||
* @description.zh-CN 两端路径结尾链接方式
|
||||
* @default 'miter'
|
||||
*/
|
||||
lineCap?: 'bevel' | 'round' | 'miter';
|
||||
/**
|
||||
* @description.en-US The style of the intersection of two path.
|
||||
* @description.zh-CN 路径交叉的连接方式
|
||||
* @default 'miter'
|
||||
*/
|
||||
lineJoin?: 'bevel' | 'round' | 'miter';
|
||||
/**
|
||||
* @description.en-US The maximum miter length.
|
||||
* @description.zh-CN 结合最大长度
|
||||
*/
|
||||
miterLimit?: number;
|
||||
}
|
||||
|
||||
interface PathProps extends CommonProps {
|
||||
/**
|
||||
* @description.en-US style of shape
|
||||
*/
|
||||
style: PathStyle;
|
||||
}
|
||||
|
||||
const Path: React.FC<PathProps> = (props) => {
|
||||
// @ts-ignore
|
||||
const { children, ...rest } = props;
|
||||
|
||||
return (
|
||||
<div
|
||||
data-attr={{
|
||||
...rest,
|
||||
type: 'path',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Path;
|
@ -1,36 +0,0 @@
|
||||
import React from 'react';
|
||||
import { CommonProps } from '../Group';
|
||||
import { CommonShapeProps } from './common';
|
||||
|
||||
export interface PolygonStyle extends CommonShapeProps {
|
||||
/**
|
||||
* @description.en-US The points of the polygon x,y
|
||||
* @description.zh-CN 组成多边形的点 x, y
|
||||
*/
|
||||
points: [number, number][];
|
||||
}
|
||||
|
||||
interface PolygonProps extends CommonProps {
|
||||
/**
|
||||
* @description.en-US style of shape
|
||||
*/
|
||||
style: PolygonStyle;
|
||||
}
|
||||
|
||||
const Polygon: React.FC<PolygonProps> = (props) => {
|
||||
// @ts-ignore
|
||||
const { children, ...rest } = props;
|
||||
|
||||
return (
|
||||
<div
|
||||
data-attr={{
|
||||
...rest,
|
||||
type: 'polygon',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Polygon;
|
@ -1,46 +0,0 @@
|
||||
import React from 'react';
|
||||
import { CommonProps } from '../Group';
|
||||
import { CommonShapeProps } from './common';
|
||||
|
||||
export interface RectStyle extends CommonShapeProps {
|
||||
/**
|
||||
* @description.en-US The radius of the rect corner.
|
||||
* @description.zh-CN 矩形圆角
|
||||
*/
|
||||
radius?: number | number[];
|
||||
/**
|
||||
* @description.en-US The width of the rect.
|
||||
* @description.zh-CN 矩形宽度
|
||||
*/
|
||||
width?: number | 'auto';
|
||||
/**
|
||||
* @description.en-US The height of the rect.
|
||||
* @description.zh-CN 矩形高度
|
||||
*/
|
||||
height?: number | 'auto';
|
||||
}
|
||||
|
||||
interface RectProps extends CommonProps {
|
||||
/**
|
||||
* @description.en-US style of shape
|
||||
*/
|
||||
style?: RectStyle;
|
||||
}
|
||||
|
||||
const Rect: React.FC<RectProps> = (props) => {
|
||||
// @ts-ignore
|
||||
const { children, ...rest } = props;
|
||||
|
||||
return (
|
||||
<div
|
||||
data-attr={{
|
||||
...rest,
|
||||
type: 'rect',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Rect;
|
@ -1,70 +0,0 @@
|
||||
import React, { CSSProperties } from 'react';
|
||||
import { CommonProps } from '../Group';
|
||||
import { CommonShapeProps } from './common';
|
||||
|
||||
export interface TextStyle extends CommonShapeProps {
|
||||
/**
|
||||
* @description.en-US text align way, affect relative position of x
|
||||
* @description.zh-CN 对齐方式,对齐的点为文字x的点
|
||||
*/
|
||||
textAlign?: 'center' | 'end' | 'left' | 'right' | 'start';
|
||||
/**
|
||||
* @description.en-US text baseline, affect relative position of y
|
||||
* @description.zh-CN 文字基线,基线在y坐标上
|
||||
*/
|
||||
textBaseline?: 'top' | 'middle' | 'bottom' | 'alphabetic' | 'hanging';
|
||||
/**
|
||||
* @description.en-US CSS font-style
|
||||
* @description.zh-CN CSS font-style
|
||||
*/
|
||||
fontStyle?: CSSProperties['fontStyle'];
|
||||
/**
|
||||
* @description.en-US CSS font-weight
|
||||
* @description.zh-CN CSS font-weight
|
||||
*/
|
||||
fontWeight?: CSSProperties['fontWeight'];
|
||||
/**
|
||||
* @description.en-US CSS font-variant
|
||||
* @description.zh-CN CSS font-variant
|
||||
*/
|
||||
fontVariant?: CSSProperties['fontVariant'];
|
||||
/**
|
||||
* @description.en-US CSS font-size
|
||||
* @description.zh-CN CSS font-size
|
||||
*/ fontSize?: CSSProperties['fontSize'];
|
||||
/**
|
||||
* @description.en-US CSS font-family
|
||||
* @description.zh-CN CSS font-family
|
||||
*/
|
||||
fontFamily?: CSSProperties['fontFamily'];
|
||||
/**
|
||||
* @description.en-US CSS line-height
|
||||
* @description.zh-CN CSS line-height
|
||||
*/
|
||||
lineHeight?: CSSProperties['lineHeight'];
|
||||
}
|
||||
|
||||
interface TextProps extends CommonProps {
|
||||
/**
|
||||
* @description.en-US style of shape
|
||||
*/
|
||||
style?: TextStyle;
|
||||
}
|
||||
|
||||
const Text: React.FC<TextProps> = (props) => {
|
||||
// @ts-ignore
|
||||
const { children, ...rest } = props;
|
||||
|
||||
return (
|
||||
<div
|
||||
data-attr={{
|
||||
...rest,
|
||||
type: 'text',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Text;
|
@ -1,114 +0,0 @@
|
||||
import { LayoutAttrs } from '../../Layout/LayoutEnums';
|
||||
import { CSSProperties } from 'react';
|
||||
|
||||
export interface GShapeProps extends Partial<LayoutAttrs> {
|
||||
/**
|
||||
* @description.en-US the color used to fill the shape, support rgb(a)/hex/gradient
|
||||
* @description.zh-CN 填充图形的颜色,支持 rgb(a)/hex/G渐变色
|
||||
*/
|
||||
fill?: string;
|
||||
/**
|
||||
* @description.en-US the color used to stroke the border of shape, support rgb(a)/hex/gradient
|
||||
* @description.zh-CN 图形描边的颜色,支持 rgb(a)/hex/G渐变色
|
||||
*/
|
||||
stroke?: string;
|
||||
/**
|
||||
* @description.en-US width of the stroke line
|
||||
* @description.zh-CN 图形描边线的宽度
|
||||
*/
|
||||
lineWidth?: string;
|
||||
/**
|
||||
* @description.en-US The lineDash of the stroke. If its type is Number[], the elements in the array are the lengths of the lineDash.
|
||||
* @description.zh-CN 描边虚线参数,数字为虚线段长度,数组时候是每一段虚线的长度。
|
||||
*/
|
||||
lineDash?: number | number[];
|
||||
/**
|
||||
* @description.en-US The color of the shadow.
|
||||
* @description.zh-CN 阴影颜色
|
||||
*/
|
||||
shadowColor?: string;
|
||||
/**
|
||||
* @description.en-US The blur level for shadow.
|
||||
* @description.zh-CN 阴影扩散大小
|
||||
*/
|
||||
shadowBlur?: number;
|
||||
/**
|
||||
* @description.en-US The horizontal offset of the shadow.
|
||||
* @description.zh-CN 阴影的水平位移
|
||||
*/
|
||||
shadowOffsetX?: number;
|
||||
/**
|
||||
* @description.en-US The vertical offset of the shadow.
|
||||
* @description.zh-CN 阴影的垂直位移
|
||||
*/
|
||||
shadowOffsetY?: number;
|
||||
/**
|
||||
* @description.en-US The filling opacity (alpha value) of the shape. The priority is higher than opacity.
|
||||
* @description.zh-CN 填充颜色透明度,优先于图形透明度
|
||||
*/
|
||||
fillOpacity?: number;
|
||||
/**
|
||||
* @description.en-US The stroke opacity (alpha value) of the shape. The priority is higher than opacity.
|
||||
* @description.zh-CN 描边颜色透明度,优先于图形透明度
|
||||
*/
|
||||
strokeOpacity?: number;
|
||||
/**
|
||||
* @description.en-US The opacity (alpha value) of the shape.
|
||||
* @description.zh-CN 图形透明度
|
||||
*/
|
||||
opacity?: number;
|
||||
/**
|
||||
* @description.en-US Cursor shape when hover on it
|
||||
* @description.zh-CN 图形上鼠标指针
|
||||
*/
|
||||
cursor?: CSSProperties['cursor'];
|
||||
}
|
||||
|
||||
export interface CommonShapeProps extends GShapeProps {
|
||||
/**
|
||||
* @description.en-US The x of the center of the Shape.
|
||||
* @description.zh-CN 图形的x坐标,定义后x绝对计算
|
||||
*/
|
||||
x?: number;
|
||||
/**
|
||||
* @description.en-US The y of the center of the Shape.
|
||||
* @description.zh-CN 图形的y坐标,定义后y绝对计算
|
||||
*/
|
||||
y?: number;
|
||||
/**
|
||||
* @description.en-US left margin of shape
|
||||
* @description.zh-CN 图形距离上一个元素的左间距
|
||||
*/
|
||||
marginLeft?: number;
|
||||
/**
|
||||
* @description.en-US top margin of shape
|
||||
* @description.zh-CN 图形距离上一个元素的上间距
|
||||
*/
|
||||
marginTop?: number;
|
||||
/**
|
||||
* @description.en-US make next shape follow inline
|
||||
* @description.zh-CN 下一个图形的定位模式,目前只能设置跟随
|
||||
*/
|
||||
next?: 'inline';
|
||||
}
|
||||
|
||||
export type GPath =
|
||||
| ['Z']
|
||||
| ['H' | 'h' | 'V' | 'v' | 'T' | 't', number]
|
||||
| ['M' | 'm' | 'L' | 'l', number, number]
|
||||
| ['S' | 's' | 'Q' | 'q', number, number, number, number]
|
||||
| ['C' | 'c', number, number, number, number, number, number]
|
||||
| ['A' | 'a', number, number, number, number, number, number, number];
|
||||
|
||||
export type Arrow =
|
||||
| boolean
|
||||
| {
|
||||
/**
|
||||
* SVG path string of arrow
|
||||
*/
|
||||
path: string;
|
||||
/**
|
||||
* @description.en-US offset distance of the arrow
|
||||
*/
|
||||
d: number;
|
||||
};
|
@ -1,78 +0,0 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import G6, { GraphData } from '@antv/g6';
|
||||
import { appenAutoShapeListener } from '../Register/event';
|
||||
|
||||
export const G6MiniDemo = ({
|
||||
nodeType,
|
||||
count = 1,
|
||||
height = 200,
|
||||
}: {
|
||||
nodeType: string;
|
||||
count: number;
|
||||
height: number;
|
||||
}) => {
|
||||
useEffect(() => {
|
||||
const data = {
|
||||
nodes: 'e'
|
||||
.repeat(count)
|
||||
.split('')
|
||||
.map((e, i) => ({
|
||||
description: 'ant_type_name_...',
|
||||
label: 'Type / ReferType',
|
||||
color: '#7262fd',
|
||||
meta: {
|
||||
creatorName: 'a_creator',
|
||||
},
|
||||
id:
|
||||
'node' +
|
||||
i +
|
||||
Math.random()
|
||||
.toString(16)
|
||||
.slice(-4),
|
||||
type: nodeType,
|
||||
})),
|
||||
edges: [],
|
||||
} as GraphData;
|
||||
|
||||
if (data && data.nodes && data.nodes.length > 1) {
|
||||
data.edges!.push({
|
||||
source: data.nodes[0].id,
|
||||
target: data.nodes[1].id,
|
||||
style: {
|
||||
endArrow: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const width = document.getElementById('container')?.clientWidth || 800;
|
||||
|
||||
const graph = new G6.Graph({
|
||||
container: 'container',
|
||||
width,
|
||||
height,
|
||||
fitCenter: true,
|
||||
modes: {
|
||||
default: ['drag-node', 'drag-canvas', 'zoom-canvas'],
|
||||
},
|
||||
layout: {
|
||||
type: 'dagre',
|
||||
rankdir: 'LR',
|
||||
},
|
||||
});
|
||||
graph.data(data);
|
||||
const time = new Date();
|
||||
graph.render();
|
||||
console.log(
|
||||
`${count} Nodes rendered`,
|
||||
'Render time:',
|
||||
(Number(new Date()) - Number(time)) / 1000,
|
||||
's',
|
||||
);
|
||||
appenAutoShapeListener(graph);
|
||||
return () => {
|
||||
graph.destroy();
|
||||
};
|
||||
}, [count, nodeType]);
|
||||
|
||||
return <div id="container"></div>;
|
||||
};
|
105
packages/react-node/src/ReactNode/index.md
Normal file
105
packages/react-node/src/ReactNode/index.md
Normal file
@ -0,0 +1,105 @@
|
||||
## Antd React Node
|
||||
|
||||
```jsx
|
||||
import { ArrowDownOutlined, ArrowUpOutlined } from '@ant-design/icons';
|
||||
import { Button, Card, Col, Row, Statistic } from 'antd';
|
||||
import { createReactNode } from '@antv/g6-react-node';
|
||||
import { Graph, extend } from '@antv/g6';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import type { ReactNodeProps } from '@antv/g6-react-node';
|
||||
|
||||
const Statistics: React.FC<ReactNodeProps> = ({ model }) => {
|
||||
const { value } = model.data;
|
||||
return (
|
||||
<Row gutter={16}>
|
||||
<Col span={24}>
|
||||
<Card bordered={false}>
|
||||
<Statistic
|
||||
title="Active"
|
||||
value={value}
|
||||
precision={2}
|
||||
valueStyle={{ color: value > 0 ? '#3f8600' : '#cf1322' }}
|
||||
prefix={value > 0 ? <ArrowUpOutlined /> : <ArrowDownOutlined />}
|
||||
suffix="%"
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
export default () => {
|
||||
const ref = useRef();
|
||||
const graphRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!ref.current) return;
|
||||
const container = ref.current;
|
||||
const width = container.scrollWidth;
|
||||
const height = container.scrollHeight || 500;
|
||||
|
||||
const reactNode = createReactNode(Statistics);
|
||||
|
||||
const ExtendGraph = extend(Graph, {
|
||||
nodes: {
|
||||
'react-node': reactNode,
|
||||
},
|
||||
});
|
||||
|
||||
graphRef.current = new ExtendGraph({
|
||||
container,
|
||||
width,
|
||||
height,
|
||||
modes: {
|
||||
default: [
|
||||
{
|
||||
type: 'drag-node',
|
||||
enableTransient: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
data: {
|
||||
nodes: [
|
||||
{ id: 'node0', data: { size: [200, 100], value: 0 } },
|
||||
{ id: 'node1', data: { size: [200, 100], value: 0.1128 } },
|
||||
{ id: 'node2', data: { size: [200, 100], value: -0.093 } },
|
||||
],
|
||||
edges: [
|
||||
{ id: 'edge1', source: 'node0', target: 'node1', data: {} },
|
||||
{ id: 'edge2', source: 'node0', target: 'node2', data: {} },
|
||||
],
|
||||
},
|
||||
node: {
|
||||
type: 'react-node',
|
||||
},
|
||||
});
|
||||
|
||||
return () => {
|
||||
graphRef.current.destroy();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div style={{ width: '100%', height: 500 }}>
|
||||
<div>
|
||||
<Button
|
||||
onClick={() => {
|
||||
Array.from({ length: 3 }).forEach((_, i) => {
|
||||
graphRef.current.updateData('node', {
|
||||
id: `node${i}`,
|
||||
data: {
|
||||
size: [200, 100],
|
||||
value: Math.random() * 2 - 1,
|
||||
},
|
||||
});
|
||||
});
|
||||
}}
|
||||
>
|
||||
更新数据
|
||||
</Button>
|
||||
</div>
|
||||
<div ref={ref}></div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
@ -1,11 +0,0 @@
|
||||
export { default as Group } from './Group';
|
||||
export { default as Circle } from './Shape/Circle';
|
||||
export { default as Ellipse } from './Shape/Ellipse';
|
||||
export { default as Image } from './Shape/Image';
|
||||
export { default as Marker } from './Shape/Marker';
|
||||
export { default as Polygon } from './Shape/Polygon';
|
||||
export { default as Rect } from './Shape/Rect';
|
||||
export { default as Path } from './Shape/Path';
|
||||
export { default as Text } from './Shape/Text';
|
||||
export { createNodeFromReact } from '../Register/register';
|
||||
export { appenAutoShapeListener } from '../Register/event';
|
2
packages/react-node/src/ReactNode/index.tsx
Normal file
2
packages/react-node/src/ReactNode/index.tsx
Normal file
@ -0,0 +1,2 @@
|
||||
export { createReactNode } from './node';
|
||||
export type { ReactNodeProps } from './typings';
|
112
packages/react-node/src/ReactNode/node.tsx
Normal file
112
packages/react-node/src/ReactNode/node.tsx
Normal file
@ -0,0 +1,112 @@
|
||||
import { HTML } from '@antv/g';
|
||||
import type { NodeDisplayModel } from '@antv/g6';
|
||||
import { Extensions } from '@antv/g6';
|
||||
import type { State } from '@antv/g6/lib/types/item';
|
||||
import type { NodeShapeMap, NodeUserModelData } from '@antv/g6/lib/types/node';
|
||||
import { throttle } from '@antv/util';
|
||||
import React from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
|
||||
const ShapeCollection: Record<string, any> = new Map();
|
||||
|
||||
export const createReactNode = (
|
||||
Component: (props: {
|
||||
model?: NodeDisplayModel;
|
||||
states?: State[];
|
||||
}) => React.ReactElement,
|
||||
): any => {
|
||||
class ReactNode extends Extensions.RectNode {
|
||||
drawKeyShape(
|
||||
model: NodeDisplayModel,
|
||||
shapeMap: NodeShapeMap,
|
||||
diffData?:
|
||||
| { previous: NodeUserModelData; current: NodeUserModelData }
|
||||
| undefined,
|
||||
diffState?: { previous: State[]; current: State[] } | undefined,
|
||||
) {
|
||||
const { data } = model;
|
||||
const { size: [width, height] = [200, 50] } = data as any;
|
||||
|
||||
const html = this.upsertShape(
|
||||
'html',
|
||||
'keyShape',
|
||||
{
|
||||
x: -width / 2,
|
||||
y: -height / 2,
|
||||
width,
|
||||
height,
|
||||
anchor: '0.5 0.5',
|
||||
pointerEvents: 'auto',
|
||||
},
|
||||
{ shapeMap, model, diffData, diffState },
|
||||
) as HTML;
|
||||
|
||||
html.isMutationObserved = true;
|
||||
html.addEventListener('DOMNodeInserted', () => {
|
||||
const dom = html.getDomElement();
|
||||
const content = (
|
||||
<Component model={model} states={diffState?.current || []} />
|
||||
);
|
||||
const root = createRoot(dom);
|
||||
root.render(content);
|
||||
|
||||
// drag logic
|
||||
dom.addEventListener('pointerdown', (event) => {
|
||||
const { clientX, clientY } = event;
|
||||
|
||||
let baseX = clientX;
|
||||
let baseY = clientY;
|
||||
|
||||
const onMouseMove = throttle(
|
||||
(event: PointerEvent) => {
|
||||
dom.style.userSelect = 'none';
|
||||
|
||||
const diffX = event.clientX - baseX;
|
||||
const diffY = event.clientY - baseY;
|
||||
baseX = event.clientX;
|
||||
baseY = event.clientY;
|
||||
|
||||
const node = this.graph.getNodeData(model.id);
|
||||
const { x: nodeX = 0, y: nodeY = 0 } = node?.data || {};
|
||||
|
||||
this.graph.updateNodePosition(
|
||||
{
|
||||
...node,
|
||||
data: { ...node?.data, x: nodeX + diffX, y: nodeY + diffY },
|
||||
},
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
},
|
||||
50,
|
||||
{ leading: true, trailing: true },
|
||||
) as EventListener;
|
||||
|
||||
document.addEventListener('pointermove', onMouseMove);
|
||||
|
||||
dom.addEventListener(
|
||||
'pointerup',
|
||||
() => {
|
||||
dom.style.userSelect = 'auto';
|
||||
document.removeEventListener('pointermove', onMouseMove);
|
||||
},
|
||||
{ once: true },
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
html.addEventListener('destroy', () => {
|
||||
html.removeAllEventListeners();
|
||||
html.getDomElement().remove();
|
||||
});
|
||||
|
||||
html.getRenderBounds = html.getBounds;
|
||||
|
||||
ShapeCollection.set(model.id, html);
|
||||
|
||||
return html;
|
||||
}
|
||||
}
|
||||
|
||||
return ReactNode;
|
||||
};
|
7
packages/react-node/src/ReactNode/typings.ts
Normal file
7
packages/react-node/src/ReactNode/typings.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import type { NodeDisplayModel } from '@antv/g6';
|
||||
import type { State } from '@antv/g6/lib/types/item';
|
||||
|
||||
export type ReactNodeProps = {
|
||||
model: NodeDisplayModel;
|
||||
states: State[];
|
||||
};
|
147
packages/react-node/src/ReactNode2.md
Normal file
147
packages/react-node/src/ReactNode2.md
Normal file
@ -0,0 +1,147 @@
|
||||
## Complex React Node
|
||||
|
||||
```jsx
|
||||
import { ArrowDownOutlined, ArrowUpOutlined } from '@ant-design/icons';
|
||||
import { List, Form, Checkbox, Button, Input, Table } from 'antd';
|
||||
import { createReactNode } from '@antv/g6-react-node';
|
||||
import { Graph, extend } from '@antv/g6';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import type { ReactNodeProps } from '@antv/g6-react-node';
|
||||
|
||||
const ComplexNode: React.FC<ReactNodeProps> = ({ model }) => {
|
||||
const { nodeType } = model.data;
|
||||
|
||||
let content = null;
|
||||
|
||||
if (nodeType === 'form') {
|
||||
content = (
|
||||
<Form
|
||||
name="basic"
|
||||
labelCol={{ span: 8 }}
|
||||
wrapperCol={{ span: 16 }}
|
||||
style={{ maxWidth: 600 }}
|
||||
initialValues={{ remember: true }}
|
||||
autoComplete="off"
|
||||
>
|
||||
<Form.Item
|
||||
label="Username"
|
||||
name="username"
|
||||
rules={[{ required: true, message: 'Please input your username!' }]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="Password"
|
||||
name="password"
|
||||
rules={[{ required: true, message: 'Please input your password!' }]}
|
||||
>
|
||||
<Input.Password />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="remember"
|
||||
valuePropName="checked"
|
||||
wrapperCol={{ offset: 8, span: 16 }}
|
||||
>
|
||||
<Checkbox>Remember me</Checkbox>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item wrapperCol={{ offset: 8, span: 16 }}>
|
||||
<Button type="primary" htmlType="submit">
|
||||
Submit
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
} else if (nodeType === 'table') {
|
||||
content = (
|
||||
<Table
|
||||
dataSource={[{ key: '1', name: 'Table Item' }]}
|
||||
columns={[{ title: 'Title', dataIndex: 'name', key: 'name' }]}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
content = (
|
||||
<List
|
||||
size="small"
|
||||
header={<div>List</div>}
|
||||
bordered
|
||||
dataSource={['List Item']}
|
||||
renderItem={(item) => <List.Item>{item}</List.Item>}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
padding: 10,
|
||||
backgroundColor: 'white',
|
||||
boxShadow:
|
||||
'0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 9px 28px 8px rgba(0, 0, 0, 0.05)',
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default () => {
|
||||
const ref = useRef();
|
||||
const graphRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!ref.current) return;
|
||||
const container = ref.current;
|
||||
const width = container.scrollWidth;
|
||||
const height = container.scrollHeight || 500;
|
||||
|
||||
const reactNode = createReactNode(ComplexNode);
|
||||
|
||||
const ExtendGraph = extend(Graph, {
|
||||
nodes: {
|
||||
'react-node': reactNode,
|
||||
},
|
||||
});
|
||||
|
||||
graphRef.current = new ExtendGraph({
|
||||
container,
|
||||
width,
|
||||
height,
|
||||
modes: {
|
||||
default: [
|
||||
{
|
||||
type: 'drag-node',
|
||||
enableTransient: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
data: {
|
||||
nodes: [
|
||||
{ id: 'node0', data: { size: [200, 100], nodeType: 'list' } },
|
||||
{ id: 'node1', data: { size: [280, 100], nodeType: 'form' } },
|
||||
{ id: 'node2', data: { size: [200, 100], nodeType: 'table' } },
|
||||
],
|
||||
edges: [
|
||||
{ id: 'edge1', source: 'node0', target: 'node1', data: {} },
|
||||
{ id: 'edge2', source: 'node0', target: 'node2', data: {} },
|
||||
],
|
||||
},
|
||||
node: {
|
||||
type: 'react-node',
|
||||
},
|
||||
});
|
||||
|
||||
return () => {
|
||||
graphRef.current.destroy();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div style={{ width: '100%', height: 500 }}>
|
||||
<div ref={ref}></div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
@ -1,64 +0,0 @@
|
||||
import { IShape } from '@antv/g-base';
|
||||
import { IAbstractGraph, IG6GraphEvent, Item } from '@antv/g6-core';
|
||||
|
||||
export type ShapeEventListner = (
|
||||
event: IG6GraphEvent,
|
||||
node: Item | null,
|
||||
shape: IShape,
|
||||
graph: IAbstractGraph,
|
||||
) => void;
|
||||
|
||||
export interface EventAttrs {
|
||||
onClick?: ShapeEventListner;
|
||||
onDBClick?: ShapeEventListner;
|
||||
onMouseEnter?: ShapeEventListner;
|
||||
onMouseMove?: ShapeEventListner;
|
||||
onMouseOut?: ShapeEventListner;
|
||||
onMouseOver?: ShapeEventListner;
|
||||
onMouseLeave?: ShapeEventListner;
|
||||
onMouseDown?: ShapeEventListner;
|
||||
onMouseUp?: ShapeEventListner;
|
||||
onDragStart?: ShapeEventListner;
|
||||
onDrag?: ShapeEventListner;
|
||||
onDragEnd?: ShapeEventListner;
|
||||
onDragEnter?: ShapeEventListner;
|
||||
onDragLeave?: ShapeEventListner;
|
||||
onDragOver?: ShapeEventListner;
|
||||
onDrop?: ShapeEventListner;
|
||||
onContextMenu?: ShapeEventListner;
|
||||
}
|
||||
|
||||
const propsToEventMap = {
|
||||
click: 'onClick',
|
||||
dblclick: 'onDBClick',
|
||||
mouseenter: 'onMouseEnter',
|
||||
mousemove: 'onMouseMove',
|
||||
mouseout: 'onMouseOut',
|
||||
mouseover: 'onMouseOver',
|
||||
mouseleave: 'onMouseLeave',
|
||||
mousedown: 'onMouseDown',
|
||||
mouseup: 'onMouseUp',
|
||||
dragstart: 'onDragStart',
|
||||
drag: 'onDrag',
|
||||
dragend: 'onDragEnd',
|
||||
dragenter: 'onDragEnter',
|
||||
dragleave: 'onDragLeave',
|
||||
dragover: 'onDragOver',
|
||||
drop: 'onDrop',
|
||||
contextmenu: 'onContextMenu',
|
||||
};
|
||||
|
||||
export function appenAutoShapeListener(graph: IAbstractGraph) {
|
||||
Object.entries(propsToEventMap).map(([eventName, propName]) => {
|
||||
graph.on(`node:${eventName}`, (evt) => {
|
||||
const shape = evt.shape;
|
||||
const item = evt.item;
|
||||
const graph = evt.currentTarget as IAbstractGraph;
|
||||
const func = shape?.get(propName) as ShapeEventListner;
|
||||
|
||||
if (func && item) {
|
||||
func(evt, item, shape, graph);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
import { ReactElement } from 'react';
|
||||
import { LayoutAttrs } from '../Layout/LayoutEnums';
|
||||
|
||||
export interface RawNode {
|
||||
type: string;
|
||||
attrs: { [key: string]: any } & Partial<LayoutAttrs>;
|
||||
children: RawNode[];
|
||||
props: { [key: string]: any };
|
||||
}
|
||||
|
||||
const getShapeFromReact = (REl: ReactElement): RawNode => {
|
||||
if (typeof REl === 'string') {
|
||||
return REl;
|
||||
}
|
||||
if (typeof REl.type === 'string') {
|
||||
const data = REl.props['data-attr'] || {};
|
||||
const { style: attrs = {}, type, ...props } = data;
|
||||
let { children: ochildren } = REl.props;
|
||||
if (type === 'text') {
|
||||
attrs.text = ochildren?.join ? ochildren.join('') : ochildren;
|
||||
return {
|
||||
type,
|
||||
attrs,
|
||||
props,
|
||||
children: [],
|
||||
};
|
||||
}
|
||||
let children = [];
|
||||
if (typeof ochildren === 'object' && ochildren?.length) {
|
||||
children = ochildren
|
||||
.filter((e: any) => !!e)
|
||||
.reduce((a: any, b: any) => a.concat(b.concat ? b : [b]), [])
|
||||
.map((e: ReactElement) => getShapeFromReact(e));
|
||||
} else if (ochildren) {
|
||||
children = [getShapeFromReact(ochildren)];
|
||||
}
|
||||
|
||||
return {
|
||||
type,
|
||||
attrs,
|
||||
props,
|
||||
children,
|
||||
};
|
||||
} else {
|
||||
const Element = REl.type as any;
|
||||
try {
|
||||
return getShapeFromReact(new Element({ ...REl.props }));
|
||||
} catch (e) {
|
||||
return getShapeFromReact(Element({ ...REl.props }));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default getShapeFromReact;
|
@ -1,133 +0,0 @@
|
||||
import React, { ReactElement } from 'react';
|
||||
import {
|
||||
INode,
|
||||
IEdge,
|
||||
ICombo,
|
||||
ModelConfig,
|
||||
ShapeOptions,
|
||||
} from '@antv/g6-core/lib';
|
||||
import { IGroup, IShape } from '@antv/g-base';
|
||||
import getShapeFromReact from '@/Register/getDataFromReactNode';
|
||||
import getPositionUsingYoga, {
|
||||
LayoutedNode,
|
||||
} from '@/Layout/getPositionsUsingYoga';
|
||||
import { animateShapeWithConfig } from '@/Animation/animate';
|
||||
|
||||
export const registerNodeReact = (el: ReactElement) => {
|
||||
const result = getShapeFromReact(el);
|
||||
const target = getPositionUsingYoga(result);
|
||||
return target;
|
||||
};
|
||||
|
||||
const renderTarget = (target: LayoutedNode, group: any) => {
|
||||
let g = group;
|
||||
let keyshape = group;
|
||||
const { attrs = {}, boundaryBox, type, children, props } = target;
|
||||
if (target.type !== 'group') {
|
||||
const shape = group.addShape(target.type, {
|
||||
attrs,
|
||||
origin: {
|
||||
boundaryBox,
|
||||
type,
|
||||
children,
|
||||
},
|
||||
...props,
|
||||
});
|
||||
if (props.keyShape) {
|
||||
keyshape = shape;
|
||||
}
|
||||
animateShapeWithConfig(shape, props.animation);
|
||||
} else {
|
||||
g = group.addGroup(props);
|
||||
if (!keyshape) {
|
||||
keyshape = g;
|
||||
}
|
||||
}
|
||||
|
||||
if (target.children) {
|
||||
const keyshapes = target.children
|
||||
.map(n => renderTarget(n, g))
|
||||
.filter(e => e);
|
||||
keyshape = keyshapes.find(shape => !shape.isGroup()) || keyshape;
|
||||
}
|
||||
return keyshape;
|
||||
};
|
||||
|
||||
const getRealStructure = (target: LayoutedNode): LayoutedNode[] => {
|
||||
const { children } = target;
|
||||
target.children = [];
|
||||
let realChildren: LayoutedNode[] = [];
|
||||
for (let i = 0; i < children.length; i += 1) {
|
||||
const result = getRealStructure(children[i]);
|
||||
realChildren = realChildren.concat(result);
|
||||
}
|
||||
if (target.type !== 'group') {
|
||||
return [target, ...realChildren];
|
||||
} else {
|
||||
target.children = realChildren;
|
||||
return [target];
|
||||
}
|
||||
};
|
||||
|
||||
const diffTarget = (container: IGroup, shapeArr: LayoutedNode[]) => {
|
||||
const childrenList = [...container.getChildren()];
|
||||
|
||||
for (let i = 0; i < childrenList.length; i += 1) {
|
||||
const lastShape = childrenList[i];
|
||||
const nowShape = shapeArr[i];
|
||||
|
||||
if (!nowShape) {
|
||||
container.removeChild(lastShape, true);
|
||||
} else if (!lastShape) {
|
||||
renderTarget(nowShape, container);
|
||||
} else if (lastShape.cfg.type !== nowShape.type) {
|
||||
container.removeChild(lastShape, true);
|
||||
renderTarget(nowShape, container);
|
||||
} else {
|
||||
if (nowShape.props) {
|
||||
lastShape.cfg = {
|
||||
...lastShape.cfg,
|
||||
...nowShape.props,
|
||||
};
|
||||
}
|
||||
if (nowShape.attrs && lastShape.attr) {
|
||||
lastShape.attr(nowShape.attrs);
|
||||
}
|
||||
if (nowShape.type === 'group') {
|
||||
diffTarget(lastShape as IGroup, nowShape.children);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export function createNodeFromReact(
|
||||
Component: React.FC<{ cfg: ModelConfig }>,
|
||||
): { [key: string]: any } {
|
||||
const compileXML = (cfg: ModelConfig) =>
|
||||
registerNodeReact(<Component cfg={cfg} />);
|
||||
|
||||
return {
|
||||
draw(cfg: ModelConfig | undefined, fatherGroup: IGroup | undefined) {
|
||||
const resultTarget = compileXML(cfg || {});
|
||||
const keyshape: IShape = renderTarget(resultTarget, fatherGroup);
|
||||
return keyshape;
|
||||
},
|
||||
update(cfg: ModelConfig, node: INode | IEdge | ICombo | undefined) {
|
||||
const resultTarget = compileXML(cfg || {});
|
||||
if (node) {
|
||||
const nodeGroup = node.getContainer();
|
||||
const realTarget = getRealStructure(resultTarget);
|
||||
|
||||
diffTarget(nodeGroup, realTarget);
|
||||
}
|
||||
},
|
||||
getAnchorPoints() {
|
||||
return [
|
||||
[0, 0.5],
|
||||
[1, 0.5],
|
||||
[0.5, 1],
|
||||
[0.5, 0],
|
||||
];
|
||||
},
|
||||
};
|
||||
}
|
1
packages/react-node/src/common/registry.ts
Normal file
1
packages/react-node/src/common/registry.ts
Normal file
@ -0,0 +1 @@
|
||||
export const register = () => {};
|
@ -1 +1,2 @@
|
||||
export * from './ReactNode';
|
||||
export * from './GNode';
|
||||
|
@ -1,15 +0,0 @@
|
||||
import { Group, Rect, Text, Circle } from '@antv/g6-react-node';
|
||||
|
||||
const Node = () => {
|
||||
return (
|
||||
<Group name="node">
|
||||
<Circle style={{ r: 40 }}>
|
||||
<Rect
|
||||
style={{ width: 20, height: 20, marginTop: 10, marginLeft: 10 }}
|
||||
/>
|
||||
</Circle>
|
||||
<Text style={{ fill: '#000', fontSize: 16 }}>Text</Text>
|
||||
<Circle style={{ r: 10, x: 60, y: 0 }}></Circle>
|
||||
</Group>
|
||||
);
|
||||
};
|
@ -1,27 +1,16 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"importHelpers": true,
|
||||
"jsx": "react",
|
||||
"esModuleInterop": true,
|
||||
"sourceMap": true,
|
||||
"baseUrl": "./",
|
||||
"strict": true,
|
||||
"declaration": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"jsx": "react",
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@/*": ["src/*"],
|
||||
"@@/*": ["src/.umi/*"]
|
||||
},
|
||||
"allowSyntheticDefaultImports": true
|
||||
"@@/*": [".dumi/tmp/*"],
|
||||
"@antv/g6-react-node": ["src"],
|
||||
"@antv/g6-react-node/*": ["src/*", "*"]
|
||||
}
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"lib",
|
||||
"es",
|
||||
"dist",
|
||||
"typings",
|
||||
"**/__test__",
|
||||
"test"
|
||||
]
|
||||
"include": [".dumirc.ts", "src/**/*"]
|
||||
}
|
||||
|
2
packages/react-node/typings.d.ts
vendored
2
packages/react-node/typings.d.ts
vendored
@ -1,2 +0,0 @@
|
||||
declare module '*.css';
|
||||
declare module '*.less';
|
@ -1,3 +1,5 @@
|
||||
// @ts-nocheck
|
||||
|
||||
if (window) {
|
||||
// window.g6 = require('@antv/g6/es'); // import the source for debugging
|
||||
window.g6 = require('@antv/g6/lib'); // import the source for debugging
|
||||
@ -13,4 +15,9 @@ if (window) {
|
||||
window.util = require('@antv/util');
|
||||
window.stats = require('stats.js');
|
||||
window.g2 = require('@antv/g2');
|
||||
window.antd = require('antd');
|
||||
|
||||
window.React = require('react');
|
||||
window.ReactDOM = require('react-dom');
|
||||
window.g6ReactNode = require('@antv/g6-react-node');
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user